<commit_before>'use strict';

var Honeypot = require('project-honeypot');
var simpleRecaptcha = require('simple-recaptcha-new');
var pluginData = require('./plugin.json');
var winston = module.parent.require('winston');
var nconf = module.parent.require('nconf');
var async = module.parent.require('async');
var Meta = module.parent.require('./meta');
var user = module.parent.require('./user');
var topics = module.parent.require('./topics');
var db = module.parent.require('./database');

var akismet;
var honeypot;
var recaptchaArgs;
var pluginSettings;
var Plugin = module.exports;

pluginData.nbbId = pluginData.id.replace(/nodebb-plugin-/, '');

Plugin.load = function (params, callback) {

	var render = function (req, res, next) {
		res.render('admin/plugins/' + pluginData.nbbId, pluginData || {});
	};

	Meta.settings.get(pluginData.nbbId, function (err, settings) {
		if (err) {
			return callback(err);
		}
		if (!settings) {
			winston.warn('[plugins/' + pluginData.nbbId + '] Settings not set or could not be retrived!');
			return callback();
		}

		if (settings.akismetEnabled === 'on') {
			if (settings.akismetApiKey) {
				akismet = require('akismet').client({
					blog: nconf.get('url'),
					apiKey: settings.akismetApiKey
				});
				akismet.verifyKey(function (err, verified) {
					if (!verified) {
						winston.error('[plugins/' + pluginData.nbbId + '] Unable to verify Akismet API key.');
						akismet = null;
					}
				});
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Akismet API Key not set!');
			}
		}

		if (settings.honeypotEnabled === 'on') {
			if (settings.honeypotApiKey) {
				honeypot = Honeypot(settings.honeypotApiKey);
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Honeypot API Key not set!');
			}
		}

		if (settings.recaptchaEnabled === 'on') {
			if (settings.recaptchaPublicKey && settings.recaptchaPrivateKey) {

				recaptchaArgs = {
					publicKey: settings.recaptchaPublicKey,
					targetId: pluginData.nbbId + '-recaptcha-target',
					options: {
						// theme: settings.recaptchaTheme || 'clean',
						//todo: switch to custom theme, issue#9
						theme: 'clean',

						hl: (Meta.config.defaultLang || 'en').toLowerCase(),
						tabindex: settings.recaptchaTabindex || 0
					}
				};
			}
		}

		if (!settings.akismetMinReputationHam) {
			settings.akismetMinReputationHam = 10;
		}

		winston.info('[plugins/' + pluginData.nbbId + '] Settings loaded');
		pluginSettings = settings;

		params.router.get('/admin/plugins/' + pluginData.nbbId, params.middleware.admin.buildHeader, render);
		params.router.get('/api/admin/plugins/' + pluginData.nbbId, render);

		callback();
	});
};

Plugin.addCaptcha = function (data, callback) {
	if (recaptchaArgs) {
		var captcha = {
			label: 'Captcha',
			html: '' +
			'

' +
			'',
			styleName: pluginData.nbbId
		};
		if (data.templateData.regFormEntry && Array.isArray(data.templateData.regFormEntry)) {
			data.templateData.regFormEntry.push(captcha);
		} else {
			data.templateData.captcha = captcha;
		}
	}
	callback(null, data);
};

Plugin.onPostEdit = function(data, callback) {
	async.waterfall([
		function (next) {
			topics.getTopicField(data.post.tid, 'cid', next);
		},
		function (cid, next) {
			Plugin.checkReply({
				content: data.post.content,
				uid: data.post.uid,
				cid: cid,
				req: data.req,
			}, {type: 'post'}, next);
		},
	], callback);
};

Plugin.onTopicEdit = function(data, callback) {
	Plugin.checkReply({
		title: data.topic.title || '',
		uid: data.topic.uid,
		cid: data.topic.cid,
		req: data.req
	}, {type: 'topic'}, function(err) {
		callback(err, data);
	});
};

Plugin.onTopicPost = function(data, callback) {
	Plugin.checkReply(data, {type: 'topic'}, callback);
};

Plugin.onTopicReply = function(data, callback) {
	Plugin.checkReply(data, {type: 'post'}, callback);
};

Plugin.checkReply = function (data, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = null;
	}
	options = options || {};

	// http://akismet.com/development/api/#comment-check
	if (!akismet || !data || !data.req) {
		return callback(null, data);
	}
	var userData;
	var akismetData;
	async.waterfall([
		function (next) {
			async.parallel({
				isAdmin: function(next) {
					user.isAdministrator(data.uid, next);
				},
				isModerator: function (next) {
					user.isModerator(data.uid, data.cid, next);
				},
				userData: function(next) {
					user.getUserFields(data.uid, ['username', 'reputation', 'email'], next);
				}
			}, next);
		},
		function (results, next) {
			userData = results.userData;
			if (results.isAdmin || results.isModerator) {
				return callback(null, data);
			}
			akismetData = {
				referrer: data.req.headers['referer'],
				user_ip: data.req.ip,
				user_agent: data.req.headers['user-agent'],
				permalink: nconf.get('url').replace(/\/$/, '') + data.req.path,
				comment_content: (data.title ? data.title + '\n\n' : '') + (data.content || ''),
				comment_author: userData.username,
				comment_author_email: userData.email,
				// https://github.com/akhoury/nodebb-plugin-spam-be-gone/issues/54
				comment_type: options.type === 'topic' ? 'forum-post' : 'comment'
			};
			akismet.checkSpam(akismetData, next);
		},
		function (spam, next) {
			if (!spam) {
				return callback(null, data);
			}

			if (parseInt(userData.reputation, 10) >= parseInt(pluginSettings.akismetMinReputationHam, 10)) {
				akismet.submitHam(akismetData, function (err) {
					if (err) {
						winston.error(err);
					}
				});
			}

			winston.verbose('[plugins/' + pluginData.nbbId + '] Post "' + akismetData.comment_content + '" by uid: ' + data.uid + ' username: ' + userData.username + '@' + data.req.ip + ' was flagged as spam and rejected.');
			next(new Error('Post content was flagged as spam by Akismet.com'));
		}
	], callback);
};

Plugin.checkRegister = function (data, callback) {
	async.parallel([
		function (next) {
			Plugin._honeypotCheck(data.req, data.res, data.userData, next);
		},
		function (next) {
			Plugin._recaptchaCheck(data.req, data.res, data.userData, next);
		}
	], function (err) {
		callback(err, data);
	});
};

Plugin.onPostFlagged = function (data) {
	var flagObj = data.flag;

	// Don't do anything if flag is not for a post and not for "spam" reason
	if (flagObj.type !== 'post' || flagObj.description !== 'Spam') {
		return;
	}

	if (akismet && pluginSettings.akismetFlagReporting && parseInt(flagObj.reporter.reputation, 10) >= parseInt(pluginSettings.akismetFlagReporting, 10)) {
		async.parallel({
			userData: function (next) {
				user.getUserFields(flagObj.target.uid, ['username', 'email'], next);
			},
			permalink: function (next) {
				topics.getTopicField(flagObj.target.tid, 'slug', next);
			},
			ip: function (next) {
				db.getSortedSetRevRange('uid:' + flagObj.target.uid + ':ip', 0, 1, next);
			}
		}, function (err, data) {
			// todo: we don't have access to the req here :/
			var submitted = {
				user_ip: data.ip ? data.ip[0] : '',
				permalink: nconf.get('url').replace(/\/$/, '') + '/topic/' + data.permalink,
				comment_author: data.userData.username,
				comment_author_email: data.userData.email,
				comment_content: flagObj.target.content,
				comment_type: 'forum-post'
			};

			akismet.submitSpam(submitted, function (err) {
				if (err) {
					winston.error('Error reporting to Akismet', err, submitted);
				}

				winston.info('Spam reported to Akismet.', submitted);
			});
		});
	}
};

Plugin._honeypotCheck = function (req, res, userData, next) {
	if (honeypot && req && req.ip) {
		honeypot.query(req.ip, function (err, results) {
			if (err) {
				winston.error(err);
				next(null, userData);
			} else {
				if (results && results.found && results.type) {
					if (results.type.spammer || results.type.suspicious) {
						var message = userData.username + ' | ' + userData.email + ' was detected as ' + (results.type.spammer ? 'spammer' : 'suspicious');

						winston.warn('[plugins/' + pluginData.nbbId + '] ' + message + ' and was denied registration.');
						next(new Error(message), userData);
					} else {
						next(null, userData);
					}
				} else {
					winston.verbose('[plugins/' + pluginData.nbbId + '] username:' + userData.username + ' ip:' + req.ip + ' was not found in Honeypot database');
					next(null, userData);
				}
			}
		});
	} else {
		next(null, userData);
	}
};

Plugin._recaptchaCheck = function (req, res, userData, next) {
	if (recaptchaArgs && req && req.ip && req.body) {

		simpleRecaptcha(
			pluginSettings.recaptchaPrivateKey,
			req.ip,
			req.body['g-recaptcha-response'],
			function (err) {
				if (err) {
					var message = err.Error || 'Captcha not verified, are you a robot?';
					winston.verbose('[plugins/' + pluginData.nbbId + '] ' + message);
					next(new Error(message), userData);
				} else {
					next(null, userData);
				}
			}
		);
	} else {
		next(null, userData);
	}
};

Plugin.admin = {
	menu: function (custom_header, callback) {
		custom_header.plugins.push({
			"route": '/plugins/' + pluginData.nbbId,
			"icon": pluginData.faIcon,
			"name": pluginData.name
		});

		callback(null, custom_header);
	}
};
<commit_msg>fix post edit<commit_after>'use strict';

var Honeypot = require('project-honeypot');
var simpleRecaptcha = require('simple-recaptcha-new');
var pluginData = require('./plugin.json');
var winston = module.parent.require('winston');
var nconf = module.parent.require('nconf');
var async = module.parent.require('async');
var Meta = module.parent.require('./meta');
var user = module.parent.require('./user');
var topics = module.parent.require('./topics');
var db = module.parent.require('./database');

var akismet;
var honeypot;
var recaptchaArgs;
var pluginSettings;
var Plugin = module.exports;

pluginData.nbbId = pluginData.id.replace(/nodebb-plugin-/, '');

Plugin.load = function (params, callback) {

	var render = function (req, res, next) {
		res.render('admin/plugins/' + pluginData.nbbId, pluginData || {});
	};

	Meta.settings.get(pluginData.nbbId, function (err, settings) {
		if (err) {
			return callback(err);
		}
		if (!settings) {
			winston.warn('[plugins/' + pluginData.nbbId + '] Settings not set or could not be retrived!');
			return callback();
		}

		if (settings.akismetEnabled === 'on') {
			if (settings.akismetApiKey) {
				akismet = require('akismet').client({
					blog: nconf.get('url'),
					apiKey: settings.akismetApiKey
				});
				akismet.verifyKey(function (err, verified) {
					if (!verified) {
						winston.error('[plugins/' + pluginData.nbbId + '] Unable to verify Akismet API key.');
						akismet = null;
					}
				});
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Akismet API Key not set!');
			}
		}

		if (settings.honeypotEnabled === 'on') {
			if (settings.honeypotApiKey) {
				honeypot = Honeypot(settings.honeypotApiKey);
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Honeypot API Key not set!');
			}
		}

		if (settings.recaptchaEnabled === 'on') {
			if (settings.recaptchaPublicKey && settings.recaptchaPrivateKey) {

				recaptchaArgs = {
					publicKey: settings.recaptchaPublicKey,
					targetId: pluginData.nbbId + '-recaptcha-target',
					options: {
						// theme: settings.recaptchaTheme || 'clean',
						//todo: switch to custom theme, issue#9
						theme: 'clean',

						hl: (Meta.config.defaultLang || 'en').toLowerCase(),
						tabindex: settings.recaptchaTabindex || 0
					}
				};
			}
		}

		if (!settings.akismetMinReputationHam) {
			settings.akismetMinReputationHam = 10;
		}

		winston.info('[plugins/' + pluginData.nbbId + '] Settings loaded');
		pluginSettings = settings;

		params.router.get('/admin/plugins/' + pluginData.nbbId, params.middleware.admin.buildHeader, render);
		params.router.get('/api/admin/plugins/' + pluginData.nbbId, render);

		callback();
	});
};

Plugin.addCaptcha = function (data, callback) {
	if (recaptchaArgs) {
		var captcha = {
			label: 'Captcha',
			html: '' +
			'

' +
			'',
			styleName: pluginData.nbbId
		};
		if (data.templateData.regFormEntry && Array.isArray(data.templateData.regFormEntry)) {
			data.templateData.regFormEntry.push(captcha);
		} else {
			data.templateData.captcha = captcha;
		}
	}
	callback(null, data);
};

Plugin.onPostEdit = function(data, callback) {
	async.waterfall([
		function (next) {
			topics.getTopicField(data.post.tid, 'cid', next);
		},
		function (cid, next) {
			Plugin.checkReply({
				content: data.post.content,
				uid: data.post.uid,
				cid: cid,
				req: data.req,
			}, {type: 'post'}, function (err) {
				next(err, data);
			});
		},
	], callback);
};

Plugin.onTopicEdit = function(data, callback) {
	Plugin.checkReply({
		title: data.topic.title || '',
		uid: data.topic.uid,
		cid: data.topic.cid,
		req: data.req
	}, {type: 'topic'}, function(err) {
		callback(err, data);
	});
};

Plugin.onTopicPost = function(data, callback) {
	Plugin.checkReply(data, {type: 'topic'}, callback);
};

Plugin.onTopicReply = function(data, callback) {
	Plugin.checkReply(data, {type: 'post'}, callback);
};

Plugin.checkReply = function (data, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = null;
	}
	options = options || {};

	// http://akismet.com/development/api/#comment-check
	if (!akismet || !data || !data.req) {
		return callback(null, data);
	}
	var userData;
	var akismetData;
	async.waterfall([
		function (next) {
			async.parallel({
				isAdmin: function(next) {
					user.isAdministrator(data.uid, next);
				},
				isModerator: function (next) {
					user.isModerator(data.uid, data.cid, next);
				},
				userData: function(next) {
					user.getUserFields(data.uid, ['username', 'reputation', 'email'], next);
				}
			}, next);
		},
		function (results, next) {
			userData = results.userData;
			if (results.isAdmin || results.isModerator) {
				return callback(null, data);
			}
			akismetData = {
				referrer: data.req.headers['referer'],
				user_ip: data.req.ip,
				user_agent: data.req.headers['user-agent'],
				permalink: nconf.get('url').replace(/\/$/, '') + data.req.path,
				comment_content: (data.title ? data.title + '\n\n' : '') + (data.content || ''),
				comment_author: userData.username,
				comment_author_email: userData.email,
				// https://github.com/akhoury/nodebb-plugin-spam-be-gone/issues/54
				comment_type: options.type === 'topic' ? 'forum-post' : 'comment'
			};
			akismet.checkSpam(akismetData, next);
		},
		function (spam, next) {
			if (!spam) {
				return callback(null, data);
			}

			if (parseInt(userData.reputation, 10) >= parseInt(pluginSettings.akismetMinReputationHam, 10)) {
				akismet.submitHam(akismetData, function (err) {
					if (err) {
						winston.error(err);
					}
				});
			}

			winston.verbose('[plugins/' + pluginData.nbbId + '] Post "' + akismetData.comment_content + '" by uid: ' + data.uid + ' username: ' + userData.username + '@' + data.req.ip + ' was flagged as spam and rejected.');
			next(new Error('Post content was flagged as spam by Akismet.com'));
		}
	], callback);
};

Plugin.checkRegister = function (data, callback) {
	async.parallel([
		function (next) {
			Plugin._honeypotCheck(data.req, data.res, data.userData, next);
		},
		function (next) {
			Plugin._recaptchaCheck(data.req, data.res, data.userData, next);
		}
	], function (err) {
		callback(err, data);
	});
};

Plugin.onPostFlagged = function (data) {
	var flagObj = data.flag;

	// Don't do anything if flag is not for a post and not for "spam" reason
	if (flagObj.type !== 'post' || flagObj.description !== 'Spam') {
		return;
	}

	if (akismet && pluginSettings.akismetFlagReporting && parseInt(flagObj.reporter.reputation, 10) >= parseInt(pluginSettings.akismetFlagReporting, 10)) {
		async.parallel({
			userData: function (next) {
				user.getUserFields(flagObj.target.uid, ['username', 'email'], next);
			},
			permalink: function (next) {
				topics.getTopicField(flagObj.target.tid, 'slug', next);
			},
			ip: function (next) {
				db.getSortedSetRevRange('uid:' + flagObj.target.uid + ':ip', 0, 1, next);
			}
		}, function (err, data) {
			// todo: we don't have access to the req here :/
			var submitted = {
				user_ip: data.ip ? data.ip[0] : '',
				permalink: nconf.get('url').replace(/\/$/, '') + '/topic/' + data.permalink,
				comment_author: data.userData.username,
				comment_author_email: data.userData.email,
				comment_content: flagObj.target.content,
				comment_type: 'forum-post'
			};

			akismet.submitSpam(submitted, function (err) {
				if (err) {
					winston.error('Error reporting to Akismet', err, submitted);
				}

				winston.info('Spam reported to Akismet.', submitted);
			});
		});
	}
};

Plugin._honeypotCheck = function (req, res, userData, next) {
	if (honeypot && req && req.ip) {
		honeypot.query(req.ip, function (err, results) {
			if (err) {
				winston.error(err);
				next(null, userData);
			} else {
				if (results && results.found && results.type) {
					if (results.type.spammer || results.type.suspicious) {
						var message = userData.username + ' | ' + userData.email + ' was detected as ' + (results.type.spammer ? 'spammer' : 'suspicious');

						winston.warn('[plugins/' + pluginData.nbbId + '] ' + message + ' and was denied registration.');
						next(new Error(message), userData);
					} else {
						next(null, userData);
					}
				} else {
					winston.verbose('[plugins/' + pluginData.nbbId + '] username:' + userData.username + ' ip:' + req.ip + ' was not found in Honeypot database');
					next(null, userData);
				}
			}
		});
	} else {
		next(null, userData);
	}
};

Plugin._recaptchaCheck = function (req, res, userData, next) {
	if (recaptchaArgs && req && req.ip && req.body) {

		simpleRecaptcha(
			pluginSettings.recaptchaPrivateKey,
			req.ip,
			req.body['g-recaptcha-response'],
			function (err) {
				if (err) {
					var message = err.Error || 'Captcha not verified, are you a robot?';
					winston.verbose('[plugins/' + pluginData.nbbId + '] ' + message);
					next(new Error(message), userData);
				} else {
					next(null, userData);
				}
			}
		);
	} else {
		next(null, userData);
	}
};

Plugin.admin = {
	menu: function (custom_header, callback) {
		custom_header.plugins.push({
			"route": '/plugins/' + pluginData.nbbId,
			"icon": pluginData.faIcon,
			"name": pluginData.name
		});

		callback(null, custom_header);
	}
};
<|endoftext|>


<commit_before>'use strict';

var plugin = {};
var rewards = module.parent.require('./rewards');
var user = module.parent.require('./user');

plugin.conditions = require('./lib/conditions');
plugin.conditionals = require('./lib/conditionals');
plugin.rewards = require('./lib/rewards');

plugin.onUpvote = function (data) {
	var uid = data.owner;

	rewards.checkConditionAndRewardUser(uid, 'essentials/user.reputation', function (callback) {
		user.getUserField(uid, 'reputation', callback);
	});
};

plugin.onOnline = function (data) {
	var uid = data.uid;
	var lastOnline = data.timestamp;

	rewards.checkConditionAndRewardUser(uid, 'essentials/user.lastonline', function (callback) {
		callback(false, lastOnline);
	});
};

plugin.onPost = function (data) {
	var uid = data.post.uid;

	rewards.checkConditionAndRewardUser(uid, 'essentials/user.postcount', function (callback) {
		user.getUserField(uid, 'postcount', callback);
	});
};

module.exports = plugin;
<commit_msg>fix requires<commit_after>'use strict';

var plugin = {};
var rewards = require.main.require('./src/rewards');
var user = require.main.require('./src/user');

plugin.conditions = require('./lib/conditions');
plugin.conditionals = require('./lib/conditionals');
plugin.rewards = require('./lib/rewards');

plugin.onUpvote = function (data) {
	var uid = data.owner;

	rewards.checkConditionAndRewardUser(uid, 'essentials/user.reputation', function (callback) {
		user.getUserField(uid, 'reputation', callback);
	});
};

plugin.onOnline = function (data) {
	var uid = data.uid;
	var lastOnline = data.timestamp;

	rewards.checkConditionAndRewardUser(uid, 'essentials/user.lastonline', function (callback) {
		callback(false, lastOnline);
	});
};

plugin.onPost = function (data) {
	var uid = data.post.uid;

	rewards.checkConditionAndRewardUser(uid, 'essentials/user.postcount', function (callback) {
		user.getUserField(uid, 'postcount', callback);
	});
};

module.exports = plugin;
<|endoftext|>


<commit_before>"use strict";

const ImageData = require("./ImageData");
const aws       = require("aws-sdk");

const client  = new aws.S3({apiVersion: "2006-03-01"});

class S3 {
    /**
     * Get object data from S3 bucket
     *
     * @param String bucket
     * @param String key
     * @return Promise
     */
    static getObject(bucket, key, acl) {
        return new Promise((resolve, reject) => {
            client.getObject({ Bucket: bucket, Key: key }, (err, data) => {
                if ( err ) {
                    reject("S3 getObject failed: " + err);
                    return;
                }

                if ( "img-processed" in data.Metadata ) {
                    reject("Object was already processed.");
                    return;
                }

                resolve(new ImageData(
                    key,
                    bucket,
                    data.Body,
                    { ContentType: data.ContentType, CacheControl: data.CacheControl },
                    acl
                ));
            });
        });
    }

    /**
     * Put object data to S3 bucket
     *
     * @param String bucket
     * @param String key
     * @param Buffer buffer
     * @return Promise
     */
    static putObject(bucket, key, buffer, headers, acl) {
        return new Promise((resolve, reject) => {
            const params = {
                Bucket:       bucket,
                Key:          key,
                Body:         buffer,
                Metadata:     { "img-processed": "true" },
                ContentType:  headers.ContentType,
                CacheControl: headers.CacheControl
            };

            if ( acl ) {
                params.ACL = acl;
            }
            client.putObject(params, (err) => {
                ( err ) ? reject(err) : resolve("S3 putObject success");
            });
        });
    }

    /**
     * Put objects data to S3 bucket
     *
     * @param Array<ImageData> images
     * @return Promise.all
     */
    static putObjects(images) {
        return Promise.all(images.map((image) => {
            return new Promise((resolve, reject) => {
                S3.putObject(image.bucketName, image.fileName, image.data, image.headers, image.acl)
                .then(() => resolve(image))
                .catch((message) => reject(message));
            });
        }));
    }
}

module.exports = S3;
<commit_msg>Add console logging for put object call to try and debug resizing gif issue<commit_after>"use strict";

const ImageData = require("./ImageData");
const aws       = require("aws-sdk");

const client  = new aws.S3({apiVersion: "2006-03-01"});

class S3 {
    /**
     * Get object data from S3 bucket
     *
     * @param String bucket
     * @param String key
     * @return Promise
     */
    static getObject(bucket, key, acl) {
        return new Promise((resolve, reject) => {
            client.getObject({ Bucket: bucket, Key: key }, (err, data) => {
                if ( err ) {
                    reject("S3 getObject failed: " + err);
                    return;
                }

                if ( "img-processed" in data.Metadata ) {
                    reject("Object was already processed.");
                    return;
                }

                resolve(new ImageData(
                    key,
                    bucket,
                    data.Body,
                    { ContentType: data.ContentType, CacheControl: data.CacheControl },
                    acl
                ));
            });
        });
    }

    /**
     * Put object data to S3 bucket
     *
     * @param String bucket
     * @param String key
     * @param Buffer buffer
     * @return Promise
     */
    static putObject(bucket, key, buffer, headers, acl) {
        return new Promise((resolve, reject) => {
            const params = {
                Bucket:       bucket,
                Key:          key,
                Body:         buffer,
                Metadata:     { "img-processed": "true" },
                ContentType:  headers.ContentType,
                CacheControl: headers.CacheControl
            };

            if ( acl ) {
                params.ACL = acl;
            }
            client.putObject(params, (err) => {
                ( err ) ? reject(err) : resolve("S3 putObject success");
            });
        });
    }

    /**
     * Put objects data to S3 bucket
     *
     * @param Array<ImageData> images
     * @return Promise.all
     */
    static putObjects(images) {
        return Promise.all(images.map((image) => {
            return new Promise((resolve, reject) => {
                console.log('image.bucketName: ' + image.bucketName);
                console.log('image.fileName: ' + image.fileName);
                console.log('image.data.length: ' + image.data.length);
                console.log('image.headers: ' + image.headers);
                console.log('image.acl: ' + image.acl);
                S3.putObject(image.bucketName, image.fileName, image.data, image.headers, image.acl)
                .then(() => resolve(image))
                .catch((message) => reject(message));
            });
        }));
    }
}

module.exports = S3;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Flyout that supports always-open continuous scrolling.
 */

import * as Blockly from 'blockly/core';
import {ContinuousToolbox} from './ContinuousToolbox';
import {ContinuousFlyoutMetrics} from './ContinuousMetricsFlyout';

/**
 * Class for continuous flyout.
 */
export class ContinuousFlyout extends Blockly.VerticalFlyout {
  /** @override */
  constructor(workspaceOptions) {
    super(workspaceOptions);

    /**
     * List of scroll positions for each category.
     * @type {!Array<{name: string, position: !Object}>}
     */
    this.scrollPositions = [];

    /**
     * Target scroll position, used to smoothly scroll to a given category
     * location when selected.
     * @type {?number}
     */
    this.scrollTarget = null;

    /**
     * The percentage of the distance to the scrollTarget that should be
     * scrolled at a time. Lower values will produce a smoother, slower scroll.
     * @type {number}
     */
    this.scrollAnimationFraction = 0.3;

    /**
     * Whether to recycle blocks when refreshing the flyout. When false, do not
     * allow anything to be recycled. The default is to recycle.
     * @type {boolean}
     * @private
     */
    this.recyclingEnabled_ = true;

    this.workspace_.setMetricsManager(
        new ContinuousFlyoutMetrics(this.workspace_, this));

    this.autoClose = false;
  }

  /**
   * Gets parent toolbox.
   * Since we registered the ContinuousToolbox, we know that's its type.
   * @return {!ContinuousToolbox} Toolbox that owns this flyout.
   * @private
   */
  getParentToolbox_() {
    const toolbox = this.targetWorkspace.getToolbox();
    return /** @type {!ContinuousToolbox} */ (toolbox);
  }

  /**
   * Records scroll position for each category in the toolbox.
   * The scroll position is determined by the coordinates of each category's
   * label after the entire flyout has been rendered.
   * @package
   */
  recordScrollPositions() {
    this.scrollPositions = [];
    const categoryLabels = this.buttons_.filter((button) => button.isLabel() &&
        this.getParentToolbox_().getCategoryByName(button.getButtonText()));
    for (const button of categoryLabels) {
      if (button.isLabel()) {
        this.scrollPositions.push({
          name: button.getButtonText(),
          position: button.getPosition(),
        });
      }
    }
  }

  /**
   * Returns the scroll position for the given category name.
   * @param {string} name Category name.
   * @return {?Object} Scroll position for given category, or null if not found.
   * @package
   */
  getCategoryScrollPosition(name) {
    for (const scrollInfo of this.scrollPositions) {
      if (scrollInfo.name === name) {
        return scrollInfo.position;
      }
    }
    console.warn(`Scroll position not recorded for category ${name}`);
    return null;
  }

  /**
   * Selects an item in the toolbox based on the scroll position of the flyout.
   * @param {number} position Current scroll position of the workspace.
   * @private
   */
  selectCategoryByScrollPosition_(position) {
    // If we are currently auto-scrolling, due to selecting a category by
    // clicking on it, do not update the category selection.
    if (this.scrollTarget) {
      return;
    }
    const scaledPosition = Math.round(position / this.workspace_.scale);
    // Traverse the array of scroll positions in reverse, so we can select the
    // furthest category that the scroll position is beyond.
    for (let i = this.scrollPositions.length - 1; i >= 0; i--) {
      const category = this.scrollPositions[i];
      if (scaledPosition >= category.position.y) {
        this.getParentToolbox_().selectCategoryByName(category.name);
        return;
      }
    }
  }

  /**
   * Scrolls flyout to given position.
   * @param {number} position The x coordinate to scroll to.
   */
  scrollTo(position) {
    // Set the scroll target to either the scaled position or the lowest
    // possible scroll point, whichever is smaller.
    const metrics = this.workspace_.getMetrics();
    this.scrollTarget = Math.min(position * this.workspace_.scale,
        metrics.scrollHeight - metrics.viewHeight);

    this.stepScrollAnimation_();
  }

  /**
   * Step the scrolling animation by scrolling a fraction of the way to
   * a scroll target, and request the next frame if necessary.
   * @private
   */
  stepScrollAnimation_() {
    if (!this.scrollTarget) {
      return;
    }

    const currentScrollPos = -this.workspace_.scrollY;
    const diff = this.scrollTarget - currentScrollPos;
    if (Math.abs(diff) < 1) {
      this.workspace_.scrollbar.setY(this.scrollTarget);
      this.scrollTarget = null;
      return;
    }
    this.workspace_.scrollbar.setY(
        currentScrollPos + diff * this.scrollAnimationFraction);

    requestAnimationFrame(this.stepScrollAnimation_.bind(this));
  }

  /**
   * Add additional padding to the bottom of the flyout if needed,
   * in order to make it possible to scroll to the top of the last category.
   * @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics Content
   *    metrics for the flyout.
   * @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics View metrics
   *    for the flyout.
   * @return {number} Additional bottom padding.
   */
  calculateBottomPadding(contentMetrics, viewMetrics) {
    if (this.scrollPositions.length > 0) {
      const lastCategory =
          this.scrollPositions[this.scrollPositions.length - 1];
      const lastPosition = lastCategory.position.y * this.workspace_.scale;
      const lastCategoryHeight = contentMetrics.height - lastPosition;
      if (lastCategoryHeight < viewMetrics.height) {
        return viewMetrics.height - lastCategoryHeight;
      }
    }
    return 0;
  }

  /** @override */
  setMetrics_(xyRatio) {
    super.setMetrics_(xyRatio);
    if (this.scrollPositions) {
      this.selectCategoryByScrollPosition_(-this.workspace_.scrollY);
    }
  }

  /**
   * Overrides the position function solely to change the x coord in RTL mode.
   * The base function allows the workspace to go "under" the flyout, so
   * to calculate the left edge of the flyout in RTL you would just subtract
   * the flyout width from the total viewWidth to get x. However, in our
   * flyout, the workspace already starts at the left edge of the flyout, so
   * we don't need to subtract the flyout width again.
   * Ideally there would be a smaller method for us to override instead,
   * but for now we copy/paste this method and make our fixes.
   * @override
   */
  position() {
    if (!this.isVisible()) {
      return;
    }
    const targetWorkspaceMetrics = this.targetWorkspace.getMetrics();
    if (!targetWorkspaceMetrics) {
      // Hidden components will return null.
      return;
    }
    // Record the height for Blockly.Flyout.getMetrics_
    this.height_ = targetWorkspaceMetrics.viewHeight;

    const edgeWidth = this.width_ - this.CORNER_RADIUS;
    const edgeHeight =
        targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS;
    this.setBackgroundPath_(edgeWidth, edgeHeight);

    // Y is always 0 since this is a vertical flyout.
    const y = 0;
    let x = 0;
    // If this flyout is the toolbox flyout.
    if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) {
      // If there is a category toolbox.
      if (targetWorkspaceMetrics.toolboxWidth) {
        if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
          x = targetWorkspaceMetrics.toolboxWidth;
        } else {
          // TODO(https://github.com/google/blockly/issues/4396): Use a better
          // API to adjust this value.
          // This is the only line that changed from the original.
          x = targetWorkspaceMetrics.viewWidth;
        }
      } else {
        if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
          x = 0;
        } else {
          x = targetWorkspaceMetrics.viewWidth;
        }
      }
    } else {
      if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
        x = 0;
      } else {
        // Because the anchor point of the flyout is on the left, but we want
        // to align the right edge of the flyout with the right edge of the
        // blocklyDiv, we calculate the full width of the div minus the width
        // of the flyout.
        x = targetWorkspaceMetrics.viewWidth +
            targetWorkspaceMetrics.absoluteLeft - this.width_;
      }
    }
    this.positionAt_(this.width_, this.height_, x, y);
  }

  /**
   * @override
   */
  show(flyoutDef) {
    super.show(flyoutDef);
    this.recordScrollPositions();
    this.workspace_.resizeContents();
  }

  /**
   * Determine if this block can be recycled in the flyout.  Blocks that have no
   * variables and are not dynamic shadows can be recycled.
   * @param {!Blockly.BlockSvg} block The block to attempt to recycle.
   * @return {boolean} True if the block can be recycled.
   * @protected
   */
  blockIsRecyclable_(block) {
    if (!this.recyclingEnabled_) {
      return false;
    }

    // If the block needs to parse mutations, never recycle.
    if (block.mutationToDom && block.domToMutation) {
      return false;
    }

    for (const input of block.inputList) {
      for (const field of input.fieldRow) {
        // No variables.
        if (field instanceof Blockly.FieldVariable) {
          return false;
        }
        if (field instanceof Blockly.FieldDropdown) {
          if (field.isOptionListDynamic()) {
            return false;
          }
        }
      }
      // Check children.
      if (input.connection) {
        const targetBlock = /** @type {Blockly.BlockSvg} */
            (input.connection.targetBlock());
        if (targetBlock && !this.blockIsRecyclable_(targetBlock)) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * Sets the function used to determine whether a block is recyclable.
   * @param {function(!Blockly.BlockSvg):boolean} func The function used to
   *     determine if a block is recyclable.
   * @public
   */
  setBlockIsRecyclable(func) {
    this.blockIsRecyclable_ = func;
  }

  /**
   * Set whether the flyout can recycle blocks.
   * @param {boolean} isEnabled True to allow blocks to be recycled, false
   *     otherwise.
   * @public
   */
  setRecyclingEnabled(isEnabled) {
    this.recyclingEnabled_ = isEnabled;
  }
}
<commit_msg>fix: new category not being selected when dragging flyout (#1186)<commit_after>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Flyout that supports always-open continuous scrolling.
 */

import * as Blockly from 'blockly/core';
import {ContinuousToolbox} from './ContinuousToolbox';
import {ContinuousFlyoutMetrics} from './ContinuousMetricsFlyout';

/**
 * Class for continuous flyout.
 */
export class ContinuousFlyout extends Blockly.VerticalFlyout {
  /** @override */
  constructor(workspaceOptions) {
    super(workspaceOptions);

    /**
     * List of scroll positions for each category.
     * @type {!Array<{name: string, position: !Object}>}
     */
    this.scrollPositions = [];

    /**
     * Target scroll position, used to smoothly scroll to a given category
     * location when selected.
     * @type {?number}
     */
    this.scrollTarget = null;

    /**
     * The percentage of the distance to the scrollTarget that should be
     * scrolled at a time. Lower values will produce a smoother, slower scroll.
     * @type {number}
     */
    this.scrollAnimationFraction = 0.3;

    /**
     * Whether to recycle blocks when refreshing the flyout. When false, do not
     * allow anything to be recycled. The default is to recycle.
     * @type {boolean}
     * @private
     */
    this.recyclingEnabled_ = true;

    this.workspace_.setMetricsManager(
        new ContinuousFlyoutMetrics(this.workspace_, this));

    this.workspace_.addChangeListener((e) => {
      if (e.type === Blockly.Events.VIEWPORT_CHANGE) {
        this.selectCategoryByScrollPosition_(-this.workspace_.scrollY);
      }
    });

    this.autoClose = false;
  }

  /**
   * Gets parent toolbox.
   * Since we registered the ContinuousToolbox, we know that's its type.
   * @return {!ContinuousToolbox} Toolbox that owns this flyout.
   * @private
   */
  getParentToolbox_() {
    const toolbox = this.targetWorkspace.getToolbox();
    return /** @type {!ContinuousToolbox} */ (toolbox);
  }

  /**
   * Records scroll position for each category in the toolbox.
   * The scroll position is determined by the coordinates of each category's
   * label after the entire flyout has been rendered.
   * @package
   */
  recordScrollPositions() {
    this.scrollPositions = [];
    const categoryLabels = this.buttons_.filter((button) => button.isLabel() &&
        this.getParentToolbox_().getCategoryByName(button.getButtonText()));
    for (const button of categoryLabels) {
      if (button.isLabel()) {
        this.scrollPositions.push({
          name: button.getButtonText(),
          position: button.getPosition(),
        });
      }
    }
  }

  /**
   * Returns the scroll position for the given category name.
   * @param {string} name Category name.
   * @return {?Object} Scroll position for given category, or null if not found.
   * @package
   */
  getCategoryScrollPosition(name) {
    for (const scrollInfo of this.scrollPositions) {
      if (scrollInfo.name === name) {
        return scrollInfo.position;
      }
    }
    console.warn(`Scroll position not recorded for category ${name}`);
    return null;
  }

  /**
   * Selects an item in the toolbox based on the scroll position of the flyout.
   * @param {number} position Current scroll position of the workspace.
   * @private
   */
  selectCategoryByScrollPosition_(position) {
    // If we are currently auto-scrolling, due to selecting a category by
    // clicking on it, do not update the category selection.
    if (this.scrollTarget) {
      return;
    }
    const scaledPosition = Math.round(position / this.workspace_.scale);
    // Traverse the array of scroll positions in reverse, so we can select the
    // furthest category that the scroll position is beyond.
    for (let i = this.scrollPositions.length - 1; i >= 0; i--) {
      const category = this.scrollPositions[i];
      if (scaledPosition >= category.position.y) {
        this.getParentToolbox_().selectCategoryByName(category.name);
        return;
      }
    }
  }

  /**
   * Scrolls flyout to given position.
   * @param {number} position The x coordinate to scroll to.
   */
  scrollTo(position) {
    // Set the scroll target to either the scaled position or the lowest
    // possible scroll point, whichever is smaller.
    const metrics = this.workspace_.getMetrics();
    this.scrollTarget = Math.min(position * this.workspace_.scale,
        metrics.scrollHeight - metrics.viewHeight);

    this.stepScrollAnimation_();
  }

  /**
   * Step the scrolling animation by scrolling a fraction of the way to
   * a scroll target, and request the next frame if necessary.
   * @private
   */
  stepScrollAnimation_() {
    if (!this.scrollTarget) {
      return;
    }

    const currentScrollPos = -this.workspace_.scrollY;
    const diff = this.scrollTarget - currentScrollPos;
    if (Math.abs(diff) < 1) {
      this.workspace_.scrollbar.setY(this.scrollTarget);
      this.scrollTarget = null;
      return;
    }
    this.workspace_.scrollbar.setY(
        currentScrollPos + diff * this.scrollAnimationFraction);

    requestAnimationFrame(this.stepScrollAnimation_.bind(this));
  }

  /**
   * Add additional padding to the bottom of the flyout if needed,
   * in order to make it possible to scroll to the top of the last category.
   * @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics Content
   *    metrics for the flyout.
   * @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics View metrics
   *    for the flyout.
   * @return {number} Additional bottom padding.
   */
  calculateBottomPadding(contentMetrics, viewMetrics) {
    if (this.scrollPositions.length > 0) {
      const lastCategory =
          this.scrollPositions[this.scrollPositions.length - 1];
      const lastPosition = lastCategory.position.y * this.workspace_.scale;
      const lastCategoryHeight = contentMetrics.height - lastPosition;
      if (lastCategoryHeight < viewMetrics.height) {
        return viewMetrics.height - lastCategoryHeight;
      }
    }
    return 0;
  }

  /**
   * Overrides the position function solely to change the x coord in RTL mode.
   * The base function allows the workspace to go "under" the flyout, so
   * to calculate the left edge of the flyout in RTL you would just subtract
   * the flyout width from the total viewWidth to get x. However, in our
   * flyout, the workspace already starts at the left edge of the flyout, so
   * we don't need to subtract the flyout width again.
   * Ideally there would be a smaller method for us to override instead,
   * but for now we copy/paste this method and make our fixes.
   * @override
   */
  position() {
    if (!this.isVisible()) {
      return;
    }
    const targetWorkspaceMetrics = this.targetWorkspace.getMetrics();
    if (!targetWorkspaceMetrics) {
      // Hidden components will return null.
      return;
    }
    // Record the height for Blockly.Flyout.getMetrics_
    this.height_ = targetWorkspaceMetrics.viewHeight;

    const edgeWidth = this.width_ - this.CORNER_RADIUS;
    const edgeHeight =
        targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS;
    this.setBackgroundPath_(edgeWidth, edgeHeight);

    // Y is always 0 since this is a vertical flyout.
    const y = 0;
    let x = 0;
    // If this flyout is the toolbox flyout.
    if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) {
      // If there is a category toolbox.
      if (targetWorkspaceMetrics.toolboxWidth) {
        if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
          x = targetWorkspaceMetrics.toolboxWidth;
        } else {
          // TODO(https://github.com/google/blockly/issues/4396): Use a better
          // API to adjust this value.
          // This is the only line that changed from the original.
          x = targetWorkspaceMetrics.viewWidth;
        }
      } else {
        if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
          x = 0;
        } else {
          x = targetWorkspaceMetrics.viewWidth;
        }
      }
    } else {
      if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
        x = 0;
      } else {
        // Because the anchor point of the flyout is on the left, but we want
        // to align the right edge of the flyout with the right edge of the
        // blocklyDiv, we calculate the full width of the div minus the width
        // of the flyout.
        x = targetWorkspaceMetrics.viewWidth +
            targetWorkspaceMetrics.absoluteLeft - this.width_;
      }
    }
    this.positionAt_(this.width_, this.height_, x, y);
  }

  /**
   * @override
   */
  show(flyoutDef) {
    super.show(flyoutDef);
    this.recordScrollPositions();
    this.workspace_.resizeContents();
  }

  /**
   * Determine if this block can be recycled in the flyout.  Blocks that have no
   * variables and are not dynamic shadows can be recycled.
   * @param {!Blockly.BlockSvg} block The block to attempt to recycle.
   * @return {boolean} True if the block can be recycled.
   * @protected
   */
  blockIsRecyclable_(block) {
    if (!this.recyclingEnabled_) {
      return false;
    }

    // If the block needs to parse mutations, never recycle.
    if (block.mutationToDom && block.domToMutation) {
      return false;
    }

    for (const input of block.inputList) {
      for (const field of input.fieldRow) {
        // No variables.
        if (field instanceof Blockly.FieldVariable) {
          return false;
        }
        if (field instanceof Blockly.FieldDropdown) {
          if (field.isOptionListDynamic()) {
            return false;
          }
        }
      }
      // Check children.
      if (input.connection) {
        const targetBlock = /** @type {Blockly.BlockSvg} */
            (input.connection.targetBlock());
        if (targetBlock && !this.blockIsRecyclable_(targetBlock)) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * Sets the function used to determine whether a block is recyclable.
   * @param {function(!Blockly.BlockSvg):boolean} func The function used to
   *     determine if a block is recyclable.
   * @public
   */
  setBlockIsRecyclable(func) {
    this.blockIsRecyclable_ = func;
  }

  /**
   * Set whether the flyout can recycle blocks.
   * @param {boolean} isEnabled True to allow blocks to be recycled, false
   *     otherwise.
   * @public
   */
  setRecyclingEnabled(isEnabled) {
    this.recyclingEnabled_ = isEnabled;
  }
}
<|endoftext|>


<commit_before>const fs = require('fs');
const config = require('../config.js');
const parse = require('csv-parse');

const list = [{
  id: 'MK',
  file: 'MK.csv',
  lang: 'rus'
}, {
  id: 'AKT',
  file: 'AKT.csv',
  lang: 'rus'
}, {
  id: 'ZAG',
  file: 'ZAG.csv',
  lang: 'rus'
}, {
  id: 'DON',
  file: 'DON.csv',
  lang: 'rus'
}, {
  id: 'HOP',
  file: 'HOP.csv',
  lang: 'eng'
}, {
  id: 'BRZ',
  file: 'BRZ.csv',
  lang: 'eng'
}];

const processListItem = (client, listItem) => {
  const inputStream = fs.createReadStream(__dirname + '/../data/' + listItem.file);
  const parser = parse({columns: true, delimiter: ','});

  let authorId = 'DON';
  let lang = 'rus';

  const bulkObject = {
    body: []
  };

  const updateTerm = (term) => {
    if (term != null) {
      bulkObject.body.push({
        update: {
          _index: config.index,
          _type: 'terms',
          _id: term.wylie.split(' ').join('_')
        }
      });
      bulkObject.body.push({
        script: "if (ctx._source.containsKey('translations')) {def idx = ctx._source.translations.findIndexOf {t -> t.translatorId == authorId}; if (idx == -1) ctx._source.translations += translation; else ctx._source.translations[idx] = translation;} else {ctx._source.translations = [translation]}",
        upsert: term,
        params: {
          translation: term.translation,
          authorId: term.translation.translatorId
        }
      });
    }
  };

  const readParser = () => {
    let record;
    while ((record = parser.read()) != null) {
      if (record.wylie == null) {
        continue;
      }
      if (!!record.translations) {
        let comments = record.comment ? record.comment.split('+') : [];
        let meanings = [];
        record.translations.split('+').map((val, i) => {
          let versions = val.split(';').map(v => v.trim());
          meanings.push({
            versions: versions,
            versions_lower: versions.map(v => v.toLowerCase()),
            comment: comments.length > i ? comments[i].trim() : ''
          })
        });
        updateTerm({
          wylie: record.wylie.toLowerCase(),
          translation: {
            translatorId: listItem.id,
            language: lang,
            meanings: meanings
          }
        });
      }
    }
  };

  return new Promise((resolve, reject) => {
    parser.on('readable', readParser);

    parser.on('finish', () => {
      client.bulk(bulkObject, err => {
        if (err) {
          reject(err);
        }
        resolve({text: 'really'});
      });
    });

    parser.on('error', (err) => {
      reject(err);
    });

    inputStream.pipe(parser);
  });
};

const script = {
  title: `Add translations`,
  run: (client) => new Promise((resolve, reject) => {

    let count = 0, countErrors = 0;
    const _done = () => {
      if (countErrors) {
        reject('ERROR');
      }
      else {
        resolve({text: 'really'});
      }
    };

    list.forEach((item, i) => {
      item.run = () =>
        processListItem(client, item).then(() => {
          count++;
          console.log(item.file + ' was processed successfully');
          if (i < list.length - 1) {
            list[i + 1].run();
          }
          else {
            _done();
          }
        }, error => {
          countErrors++;
          console.log(item.file + ' processing failed!');
          console.log(error);
          if (i < list.length - 1) {
            list[i + 1].run();
          }
          else {
            _done();
          }
        })
    });

    list[0].run();
  })
};

module.exports = script;<commit_msg>bulk terms update: painless script fix<commit_after>const fs = require('fs');
const config = require('../config.js');
const parse = require('csv-parse');

const list = [{
  id: 'MK',
  file: 'MK.csv',
  lang: 'rus'
}, {
  id: 'AKT',
  file: 'AKT.csv',
  lang: 'rus'
}, {
  id: 'ZAG',
  file: 'ZAG.csv',
  lang: 'rus'
}, {
  id: 'DON',
  file: 'DON.csv',
  lang: 'rus'
}, {
  id: 'HOP',
  file: 'HOP.csv',
  lang: 'eng'
}, {
  id: 'BRZ',
  file: 'BRZ.csv',
  lang: 'eng'
}];

const processListItem = (client, listItem) => {
  const inputStream = fs.createReadStream(__dirname + '/../data/' + listItem.file);
  const parser = parse({columns: true, delimiter: ','});

  let authorId = 'DON';
  let lang = 'rus';

  const bulkObject = {
    body: []
  };

  const updateTerm = (term) => {
    if (term != null) {
      bulkObject.body.push({
        update: {
          _index: config.index,
          _type: 'terms',
          _id: term.wylie.split(' ').join('_')
        }
      });
      bulkObject.body.push({
        script: {
          inline: "if(!ctx._source.containsKey('translations')) { ctx._source.translations = [params.translation]} else { int idx = -1; for(int i = 0; i < ctx._source.translations.length; ++i) { if(ctx._source.translations[i].translatorId == params.authorId) { idx = i; break; } } if(idx == -1) { ctx._source.translations.add(params.translation); } else { ctx._source.translations[idx] = params.translation; } }",
          params: {
            translation: term.translation,
            authorId: term.translation.translatorId
          },
          "lang": "painless"
        },
        upsert: term
      });
    }
  };

  const readParser = () => {
    let record;
    while ((record = parser.read()) != null) {
      if (record.wylie == null) {
        continue;
      }
      if (!!record.translations) {
        let comments = record.comment ? record.comment.split('+') : [];
        let meanings = [];
        record.translations.split('+').map((val, i) => {
          let versions = val.split(';').map(v => v.trim());
          meanings.push({
            versions: versions,
            versions_lower: versions.map(v => v.toLowerCase()),
            comment: comments.length > i ? comments[i].trim() : ''
          })
        });
        updateTerm({
          wylie: record.wylie.toLowerCase(),
          translation: {
            translatorId: listItem.id,
            language: lang,
            meanings: meanings
          }
        });
      }
    }
  };

  return new Promise((resolve, reject) => {
    parser.on('readable', readParser);

    parser.on('finish', () => {
      client.bulk(bulkObject, err => {
        if (err) {
          reject(err);
        }
        resolve({text: 'really'});
      });
    });

    parser.on('error', (err) => {
      reject(err);
    });

    inputStream.pipe(parser);
  });
};

const script = {
  title: `Add translations`,
  run: (client) => new Promise((resolve, reject) => {

    let count = 0, countErrors = 0;
    const _done = () => {
      if (countErrors) {
        reject('ERROR');
      }
      else {
        resolve({text: 'really'});
      }
    };

    list.forEach((item, i) => {
      item.run = () =>
        processListItem(client, item).then(() => {
          count++;
          console.log(item.file + ' was processed successfully');
          if (i < list.length - 1) {
            list[i + 1].run();
          }
          else {
            _done();
          }
        }, error => {
          countErrors++;
          console.log(item.file + ' processing failed!');
          console.log(error);
          if (i < list.length - 1) {
            list[i + 1].run();
          }
          else {
            _done();
          }
        })
    });

    list[0].run();
  })
};

module.exports = script;<|endoftext|>


<commit_before>/* TODO Theme colors, by requesting from a php file that kicks back different css
 * TODO optional non-responsive menu, working same way as above
 */

/* Takes care of making sure JQuery is only loaded once per page */
var jQueryScriptOutputted = false;
function initJQuery() {
    if (typeof(jQuery) == 'undefined') {
        if (! jQueryScriptOutputted) {
            //only output the script once..
            jQueryScriptOutputted = true;
            document.write("");
        }
        setTimeout("initJQuery()", 50);
    }
    else {
        jQuery(function() {
            loaded();
        });
    }
}
initJQuery();

/* Class for writing each of our pages */
function Page(title){
    this.title=title;

    this.__str__ = function (){
        return this.title + "

"+this.contents+"

";
    }
}

/* The actual tabs */
pages = {}
pages['app'] = new Page('Apply & Enroll');
pages['app'].contents = 'Apply & Enroll Page';

pages['about'] = new Page("Programs");
pages['about'].contents = "About Page";

pages['courses'] = new Page("Student Services");

pages['campus'] = new Page("Connections");

pages['destination'] = new Page("About");

/* These are variables that might need to change at some point */
var server = "//drupalprojects.dev/";
var resources = server+"/mm/images/";

// This is kinda like Document.ready()
function loaded(){
    jQuery('head').append('<link rel="stylesheet" type="text/css" href="'+server+'/mm/mm.css">');
    var current_url = window.location.pathname;
    var cHeight = 0;
    jQuery('body').css('margin-top','28px');
    jQuery('body').prepend('

');
    var menu =
        '
'+
            '
'+
                '
'+
                    '
Home
'+
                    '
Current Students
'+
                    '
Future Students
'+
                    '
Community
'+
                    '
Employees
'+
                '
'+
            '
'+
        '
';
    for(var key in pages){
        menu += "
"+ pages[key].__str__() + "
";
    }
    menu += '
';
    menu +=
            '
' +
                '<form id="lmm_search_form" name="search_lane_new_home_page" method="get" action="http://search.lanecc.edu/search">'+
                    'Search Lane\'s Website'+
                    '<input type="text" name="q" id="lmm_q" placeholder="Search or AskLane">'+
                    '<input type="submit" value="" id="lmm_search_submit">'+
                '</form>'+
                '
'+
                    '<input name="dest" class="lmm_so" type="radio" id="lmm_search_web" checked="checked">Lane Website'+
                    '<input name="dest" class="lmm_so" type="radio" id="lmm_search_asklane">AskLane'+
                '
'+
            '
';
    jQuery('#lmm ul').append(menu);
    jQuery('#lmm').after('

');
    jQuery('#lmm').after('

');
    var lastaction = "";
    // Handle opening and closing panes
    jQuery('body').delegate('#lmm li.lmm_toplevel', 'click', function(e){
        jQuery(this).addClass('lmm_active');
        jQuery('.lmm_other_pane').hide();
        var clickedPane = jQuery('.lmm_pane_container', this);
        //if its a differnet panel that's open, fade out that panel and fade this one in
        if(clickedPane.is(":visible") && jQuery('.lmm_pane_container:visible').length == 1){
            lastaction = "close";
            clickedPane.add(jQuery('#lmm_pane_underlay')).stop().animate({
                height: 0
            }, function(){ jQuery(this).hide(); });
            clickedPane.parent('.lmm_toplevel').removeClass('lmm_active');
        }
        //swap us with someone else
        else if(jQuery('.lmm_pane_container:visible').length > 0 && lastaction != 'close'){
            lastaction = 'swap'
            var oldPane = jQuery('.lmm_pane_container:visible').not(clickedPane);
            clickedPane.height(250);
            oldPane.stop().fadeOut(200)
            clickedPane.fadeIn(200);
            oldPane.height(0);
            oldPane.parent('.lmm_toplevel').removeClass('lmm_active');
        }
        //must just want to open us
        else{
            lastaction = 'open'
            jQuery('#lmm_underlay').height(window.innerHeight);
            clickedPane.add(jQuery('#lmm_pane_underlay')).show().stop().animate({
                height: 250
            });
        }
        e.stopPropagation();
        return false;
    });
    jQuery('#lmm li.lmm_toplevel').children().click(function(e){ e.stopPropagation();});

    //allow us to click the background to close whatever's open
    jQuery('body').delegate('#lmm_underlay', 'click', function(){
        jQuery('#lmm .lmm_other .lmm_other_pane').each(function(){
            jQuery(this).hide();
        })
        jQuery('.lmm_active').removeClass('lmm_active');
        jQuery('#lmm .lmm_pane_container:visible').add(jQuery('#lmm_pane_underlay')).animate({
            height:0
        }, function(){jQuery(this).hide();});
        jQuery('#lmm_underlay').height(0);
    });

    /* Pop open a search options box */
    jQuery('body').delegate('#lmm_q', 'click', function(e){
        //shrink any existing panes
        jQuery('#lmm_pane_underlay').height(0);
        jQuery('#lmm_homes_page').stop().fadeOut(200);
        jQuery('.lmm_pane_container:visible').stop().fadeOut(200).height(0);
        jQuery('#lmm_underlay').height(window.innerHeight);
        jQuery('.lmm_active').removeClass('lmm_active');
        jQuery('#lmm_searchops').fadeIn(200);
    });
    /* Similarly, pop open the homes button box */
    jQuery('body').delegate('#lmm_logo', 'click', function(e){
        jQuery('#lmm_pane_underlay').height(0);
        jQuery('.lmm_active').removeClass('lmm_active');
        jQuery('.lmm_pane_container:visible').stop().fadeOut(200).height(0);
        if(jQuery('#lmm_homes_page').is(':visible')){
            jQuery('#lmm_homes_page').stop().fadeOut(200);
            jQuery('#lmm_underlay').height(0);
        }
        else{
            jQuery('#lmm_underlay').height(window.innerHeight);
            jQuery('#lmm_searchops').stop().fadeOut(200);
            jQuery('#lmm_homes_page').fadeIn(200);
        }
    })
    jQuery('#lmm_logo').children().click(function(e){ e.stopPropagation();});

    /* Handle the radio buttons for the search box
     * NOT using .attr on the form due to a but in jQuery 1.4.2 where attr isnt' able to correctly set
     * form actions
     */
    jQuery('body').delegate('.lmm_so', 'click',function(e){
        var dest = jQuery('.lmm_so:checked').attr('id');
        if(dest == "lmm_search_web"){
        //remove existing temp form fields that may or may not be needed
        jQuery('.lmm_temp').remove();
        jQuery('#lmm_search_form').get(0).setAttribute('action', 'http://search.lanecc.edu/search');
            jQuery('#lmm_search_form').get(0).setAttribute('method', 'get');
            jQuery('#lmm_q').attr('name','q');
        }
        else if(dest == "lmm_search_asklane"){
            jQuery('#lmm_search_form').append('<input type="hidden" name="requestType">');
            jQuery('#lmm_search_form').get(0).setAttribute('action', 'http://lanecc.intelliresponse.com/');
            jQuery('#lmm_search_form').get(0).setAttribute('method', 'post');
            jQuery('#lmm_q').attr('name','question');
        }
        jQuery('#lmm_q').focus();
        e.stopPropagation();
    });

    /* Site specific adjustments
     */
    if(current_url.indexOf('pln') > 0){//TODO adjust to full URL out of dev
        cHeight = parseInt(jQuery('#wpadminbar').css('height'));
        jQuery('#lmm').css('top',cHeight);
    }
    else if(jQuery('body').hasClass('admin-menu')){
        jQuery('#lmm').css('display','none')
    }
}
<commit_msg>More generic wordpress handling<commit_after>/* TODO Theme colors, by requesting from a php file that kicks back different css
 * TODO optional non-responsive menu, working same way as above
 */

/* Takes care of making sure JQuery is only loaded once per page */
var jQueryScriptOutputted = false;
function initJQuery() {
    if (typeof(jQuery) == 'undefined') {
        if (! jQueryScriptOutputted) {
            //only output the script once..
            jQueryScriptOutputted = true;
            document.write("");
        }
        setTimeout("initJQuery()", 50);
    }
    else {
        jQuery(function() {
            loaded();
        });
    }
}
initJQuery();

/* Class for writing each of our pages */
function Page(title){
    this.title=title;

    this.__str__ = function (){
        return this.title + "

"+this.contents+"

";
    }
}

/* The actual tabs */
pages = {}
pages['app'] = new Page('Apply & Enroll');
pages['app'].contents = 'Apply & Enroll Page';

pages['about'] = new Page("Programs");
pages['about'].contents = "About Page";

pages['courses'] = new Page("Student Services");

pages['campus'] = new Page("Connections");

pages['destination'] = new Page("About");

/* These are variables that might need to change at some point */
var server = "//drupalprojects.dev/";
var resources = server+"/mm/images/";

// This is kinda like Document.ready()
function loaded(){
    jQuery('head').append('<link rel="stylesheet" type="text/css" href="'+server+'/mm/mm.css">');
    var current_url = window.location.pathname;
    var cHeight = 0;
    jQuery('body').css('margin-top','28px');
    jQuery('body').prepend('

');
    var menu =
        '
'+
            '
'+
                '
'+
                    '
Home
'+
                    '
Current Students
'+
                    '
Future Students
'+
                    '
Community
'+
                    '
Employees
'+
                '
'+
            '
'+
        '
';
    for(var key in pages){
        menu += "
"+ pages[key].__str__() + "
";
    }
    menu += '
';
    menu +=
            '
' +
                '<form id="lmm_search_form" name="search_lane_new_home_page" method="get" action="http://search.lanecc.edu/search">'+
                    'Search Lane\'s Website'+
                    '<input type="text" name="q" id="lmm_q" placeholder="Search or AskLane">'+
                    '<input type="submit" value="" id="lmm_search_submit">'+
                '</form>'+
                '
'+
                    '<input name="dest" class="lmm_so" type="radio" id="lmm_search_web" checked="checked">Lane Website'+
                    '<input name="dest" class="lmm_so" type="radio" id="lmm_search_asklane">AskLane'+
                '
'+
            '
';
    jQuery('#lmm ul').append(menu);
    jQuery('#lmm').after('

');
    jQuery('#lmm').after('

');
    var lastaction = "";
    // Handle opening and closing panes
    jQuery('body').delegate('#lmm li.lmm_toplevel', 'click', function(e){
        jQuery(this).addClass('lmm_active');
        jQuery('.lmm_other_pane').hide();
        var clickedPane = jQuery('.lmm_pane_container', this);
        //if its a differnet panel that's open, fade out that panel and fade this one in
        if(clickedPane.is(":visible") && jQuery('.lmm_pane_container:visible').length == 1){
            lastaction = "close";
            clickedPane.add(jQuery('#lmm_pane_underlay')).stop().animate({
                height: 0
            }, function(){ jQuery(this).hide(); });
            clickedPane.parent('.lmm_toplevel').removeClass('lmm_active');
        }
        //swap us with someone else
        else if(jQuery('.lmm_pane_container:visible').length > 0 && lastaction != 'close'){
            lastaction = 'swap'
            var oldPane = jQuery('.lmm_pane_container:visible').not(clickedPane);
            clickedPane.height(250);
            oldPane.stop().fadeOut(200)
            clickedPane.fadeIn(200);
            oldPane.height(0);
            oldPane.parent('.lmm_toplevel').removeClass('lmm_active');
        }
        //must just want to open us
        else{
            lastaction = 'open'
            jQuery('#lmm_underlay').height(window.innerHeight);
            clickedPane.add(jQuery('#lmm_pane_underlay')).show().stop().animate({
                height: 250
            });
        }
        e.stopPropagation();
        return false;
    });
    jQuery('#lmm li.lmm_toplevel').children().click(function(e){ e.stopPropagation();});

    //allow us to click the background to close whatever's open
    jQuery('body').delegate('#lmm_underlay', 'click', function(){
        jQuery('#lmm .lmm_other .lmm_other_pane').each(function(){
            jQuery(this).hide();
        })
        jQuery('.lmm_active').removeClass('lmm_active');
        jQuery('#lmm .lmm_pane_container:visible').add(jQuery('#lmm_pane_underlay')).animate({
            height:0
        }, function(){jQuery(this).hide();});
        jQuery('#lmm_underlay').height(0);
    });

    /* Pop open a search options box */
    jQuery('body').delegate('#lmm_q', 'click', function(e){
        //shrink any existing panes
        jQuery('#lmm_pane_underlay').height(0);
        jQuery('#lmm_homes_page').stop().fadeOut(200);
        jQuery('.lmm_pane_container:visible').stop().fadeOut(200).height(0);
        jQuery('#lmm_underlay').height(window.innerHeight);
        jQuery('.lmm_active').removeClass('lmm_active');
        jQuery('#lmm_searchops').fadeIn(200);
    });
    /* Similarly, pop open the homes button box */
    jQuery('body').delegate('#lmm_logo', 'click', function(e){
        jQuery('#lmm_pane_underlay').height(0);
        jQuery('.lmm_active').removeClass('lmm_active');
        jQuery('.lmm_pane_container:visible').stop().fadeOut(200).height(0);
        if(jQuery('#lmm_homes_page').is(':visible')){
            jQuery('#lmm_homes_page').stop().fadeOut(200);
            jQuery('#lmm_underlay').height(0);
        }
        else{
            jQuery('#lmm_underlay').height(window.innerHeight);
            jQuery('#lmm_searchops').stop().fadeOut(200);
            jQuery('#lmm_homes_page').fadeIn(200);
        }
    })
    jQuery('#lmm_logo').children().click(function(e){ e.stopPropagation();});

    /* Handle the radio buttons for the search box
     * NOT using .attr on the form due to a but in jQuery 1.4.2 where attr isnt' able to correctly set
     * form actions
     */
    jQuery('body').delegate('.lmm_so', 'click',function(e){
        var dest = jQuery('.lmm_so:checked').attr('id');
        if(dest == "lmm_search_web"){
        //remove existing temp form fields that may or may not be needed
        jQuery('.lmm_temp').remove();
        jQuery('#lmm_search_form').get(0).setAttribute('action', 'http://search.lanecc.edu/search');
            jQuery('#lmm_search_form').get(0).setAttribute('method', 'get');
            jQuery('#lmm_q').attr('name','q');
        }
        else if(dest == "lmm_search_asklane"){
            jQuery('#lmm_search_form').append('<input type="hidden" name="requestType">');
            jQuery('#lmm_search_form').get(0).setAttribute('action', 'http://lanecc.intelliresponse.com/');
            jQuery('#lmm_search_form').get(0).setAttribute('method', 'post');
            jQuery('#lmm_q').attr('name','question');
        }
        jQuery('#lmm_q').focus();
        e.stopPropagation();
    });

    /* Site specific adjustments
     */
    if(jQuery('body').hasClass('admin-bar')){ //Wordpress
        cHeight = parseInt(jQuery('#wpadminbar').css('height'));
        jQuery('#lmm').css('top',cHeight);
    }
    else if(jQuery('body').hasClass('admin-menu')){ //Authenticated Drupal
        jQuery('#lmm').css('display','none')
    }
}
<|endoftext|>


<commit_before>export default {
  el: {
    colorpicker: {
      confirm: 'YA',
      clear: 'Kosongkan'
    },
    datepicker: {
      now: 'Sekarang',
      today: 'Hari ini',
      cancel: 'Batal',
      clear: 'Kosongkan',
      confirm: 'YA',
      selectDate: 'Pilih tanggal',
      selectTime: 'Pilih waktu',
      startDate: 'Tanggal Mulai',
      startTime: 'Waktu Mulai',
      endDate: 'Tanggal Selesai',
      endTime: 'Waktu Selesai',
      year: 'Tahun',
      month1: 'Januari',
      month2: 'Februari',
      month3: 'Maret',
      month4: 'April',
      month5: 'Mei',
      month6: 'Juni',
      month7: 'Juli',
      month8: 'Agustus',
      month9: 'September',
      month10: 'Oktober',
      month11: 'November',
      month12: 'Desember',
      // week: 'minggu',
      weeks: {
        sun: 'Min',
        mon: 'Sen',
        tue: 'Sel',
        wed: 'Rab',
        thu: 'Kam',
        fri: 'Jum',
        sat: 'Sab'
      },
      months: {
        jan: 'Jan',
        feb: 'Feb',
        mar: 'Mar',
        apr: 'Apr',
        may: 'Mei',
        jun: 'Jun',
        jul: 'Jul',
        aug: 'Agu',
        sep: 'Sep',
        oct: 'Okt',
        nov: 'Nov',
        dec: 'Des'
      }
    },
    select: {
      loading: 'Memuat',
      noMatch: 'Tidak ada data yang cocok',
      noData: 'Tidak ada data',
      placeholder: 'Pilih'
    },
    cascader: {
      noMatch: 'Tidak ada data yang cocok',
      placeholder: 'Pilih'
    },
    pagination: {
      goto: 'Pergi ke',
      pagesize: '/page',
      total: 'Total {total}',
      pageClassifier: ''
    },
    messagebox: {
      title: 'Pesan',
      confirm: 'YA',
      cancel: 'Batal',
      error: 'Masukan ilegal'
    },
    upload: {
      delete: 'Hapus',
      preview: 'Pratinjau',
      continue: 'Lanjutkan'
    },
    table: {
      emptyText: 'Tidak Ada Data',
      confirmFilter: 'Konfirmasi',
      resetFilter: 'Atur Ulang',
      clearFilter: 'Semua',
      sumText: 'Sum' // to be translated
    },
    tree: {
      emptyText: 'Tidak Ada Data'
    },
    transfer: {
      noMatch: 'Tidak ada data yang cocok',
      noData: 'Tidak ada data',
      titles: ['List 1', 'List 2'], // to be translated
      filterPlaceholder: 'Enter keyword', // to be translated
      noCheckedFormat: '{total} items', // to be translated
      hasCheckedFormat: '{checked}/{total} checked' // to be translated
    }
  }
};
<commit_msg>Bahasa translation for v1.3 (#4579)<commit_after>export default {
  el: {
    colorpicker: {
      confirm: 'Pilih',
      clear: 'Kosongkan'
    },
    datepicker: {
      now: 'Sekarang',
      today: 'Hari ini',
      cancel: 'Batal',
      clear: 'Kosongkan',
      confirm: 'Ya',
      selectDate: 'Pilih tanggal',
      selectTime: 'Pilih waktu',
      startDate: 'Tanggal Mulai',
      startTime: 'Waktu Mulai',
      endDate: 'Tanggal Selesai',
      endTime: 'Waktu Selesai',
      year: 'Tahun',
      month1: 'Januari',
      month2: 'Februari',
      month3: 'Maret',
      month4: 'April',
      month5: 'Mei',
      month6: 'Juni',
      month7: 'Juli',
      month8: 'Agustus',
      month9: 'September',
      month10: 'Oktober',
      month11: 'November',
      month12: 'Desember',
      // week: 'minggu',
      weeks: {
        sun: 'Min',
        mon: 'Sen',
        tue: 'Sel',
        wed: 'Rab',
        thu: 'Kam',
        fri: 'Jum',
        sat: 'Sab'
      },
      months: {
        jan: 'Jan',
        feb: 'Feb',
        mar: 'Mar',
        apr: 'Apr',
        may: 'Mei',
        jun: 'Jun',
        jul: 'Jul',
        aug: 'Agu',
        sep: 'Sep',
        oct: 'Okt',
        nov: 'Nov',
        dec: 'Des'
      }
    },
    select: {
      loading: 'Memuat',
      noMatch: 'Tidak ada data yg cocok',
      noData: 'Tidak ada data',
      placeholder: 'Pilih'
    },
    cascader: {
      noMatch: 'Tidak ada data yg cocok',
      placeholder: 'Pilih'
    },
    pagination: {
      goto: 'Pergi ke',
      pagesize: '/laman',
      total: 'Total {total}',
      pageClassifier: ''
    },
    messagebox: {
      title: 'Pesan',
      confirm: 'Ya',
      cancel: 'Batal',
      error: 'Masukan ilegal'
    },
    upload: {
      delete: 'Hapus',
      preview: 'Pratinjau',
      continue: 'Lanjutkan'
    },
    table: {
      emptyText: 'Tidak ada data',
      confirmFilter: 'Konfirmasi',
      resetFilter: 'Atur ulang',
      clearFilter: 'Semua',
      sumText: 'Jml'
    },
    tree: {
      emptyText: 'Tidak ada data'
    },
    transfer: {
      noMatch: 'Tidak ada data yg cocok',
      noData: 'Tidak ada data',
      titles: ['Senarai 1', 'Senarai 2'],
      filterPlaceholder: 'Masukan kata kunci',
      noCheckedFormat: '{total} butir',
      hasCheckedFormat: '{checked}/{total} terpilih'
    }
  }
};
<|endoftext|>


<commit_before>// Localizations for UI text.
// Keys correspond to applicable `lang` values, delimited by an underscore.
// Days and months must be listed in the order they should display.

const locales = {
  'en_en-US_en-UK': {
    days: [
      `Su`,
      `Mo`,
      `Tu`,
      `We`,
      `Th`,
      `Fr`,
      `Sa`
    ],
    months: [
      `January`,
      `February`,
      `March`,
      `April`,
      `May`,
      `June`,
      `July`,
      `August`,
      `September`,
      `October`,
      `November`,
      `December`
    ]
  },
  /* Simplified Chinese */
  'zh_zh-CN': {
    days: [
      `星期天`,
      `星期一`,
      `星期二`,
      `星期三`,
      `星期四`,
      `星期五`,
      `星期六`
    ],
    months: [
      `一月`,
      `二月`,
      `三月`,
      `四月`,
      `五月`,
      `六月`,
      `七月`,
      `八月`,
      `九月`,
      `十月`,
      `十一月`,
      `十二月`
    ]
  },
  /* Simplified Chinese, informal*/
  'zh-Hans_zh-Hans-CN': {
    days: [
      `周日`,
      `周一`,
      `周二`,
      `周三`,
      `周四`,
      `周五`,
      `周六`
    ],
    months: [
      `一月`,
      `二月`,
      `三月`,
      `四月`,
      `五月`,
      `六月`,
      `七月`,
      `八月`,
      `九月`,
      `十月`,
      `十一月`,
      `十二月`
    ]
  },
  /* Traditional Chinese */
  'zh-Hant_zh-Hant-TW': {
    days: [
      `週日`,
      `週一`,
      `週二`,
      `週三`,
      `週四`,
      `週五`,
      `週六`
    ],
    months: [
      `一月`,
      `二月`,
      `三月`,
      `四月`,
      `五月`,
      `六月`,
      `七月`,
      `八月`,
      `九月`,
      `十月`,
      `十一月`,
      `十二月`
    ]
  },
  /* German (Germany) */
  'de_de-DE': {
    days: [
      `Sonntag`,
      `Montag`,
      `Dienstag`,
      `Mittwoch`,
      `Donnerstag`,
      `Freitag`,
      `Samstag`
    ],
    months: [
      `Januar`,
      `Februar`,
      `März`,
      `April`,
      `Mai`,
      `Juni`,
      `Juli`,
      `August`,
      `September`,
      `Oktober`,
      `November`,
      `Dezember`
    ]
  },
  'test_test-TEST': {
    days: [
      `Foo`,
      `Mon`,
      `Tue`,
      `Wed`,
      `Thu`,
      `Fri`,
      `Sat`
    ],
    months: [
      `Foo`,
      `February`,
      `March`,
      `April`,
      `May`,
      `June`,
      `July`,
      `August`,
      `September`,
      `October`,
      `November`,
      `December`
    ]
  }
};

export default locales;
<commit_msg>Add dutch locale<commit_after>// Localizations for UI text.
// Keys correspond to applicable `lang` values, delimited by an underscore.
// Days and months must be listed in the order they should display.

const locales = {
  'en_en-US_en-UK': {
    days: [
      `Su`,
      `Mo`,
      `Tu`,
      `We`,
      `Th`,
      `Fr`,
      `Sa`
    ],
    months: [
      `January`,
      `February`,
      `March`,
      `April`,
      `May`,
      `June`,
      `July`,
      `August`,
      `September`,
      `October`,
      `November`,
      `December`
    ]
  },
  /* Simplified Chinese */
  'zh_zh-CN': {
    days: [
      `星期天`,
      `星期一`,
      `星期二`,
      `星期三`,
      `星期四`,
      `星期五`,
      `星期六`
    ],
    months: [
      `一月`,
      `二月`,
      `三月`,
      `四月`,
      `五月`,
      `六月`,
      `七月`,
      `八月`,
      `九月`,
      `十月`,
      `十一月`,
      `十二月`
    ]
  },
  /* Simplified Chinese, informal*/
  'zh-Hans_zh-Hans-CN': {
    days: [
      `周日`,
      `周一`,
      `周二`,
      `周三`,
      `周四`,
      `周五`,
      `周六`
    ],
    months: [
      `一月`,
      `二月`,
      `三月`,
      `四月`,
      `五月`,
      `六月`,
      `七月`,
      `八月`,
      `九月`,
      `十月`,
      `十一月`,
      `十二月`
    ]
  },
  /* Traditional Chinese */
  'zh-Hant_zh-Hant-TW': {
    days: [
      `週日`,
      `週一`,
      `週二`,
      `週三`,
      `週四`,
      `週五`,
      `週六`
    ],
    months: [
      `一月`,
      `二月`,
      `三月`,
      `四月`,
      `五月`,
      `六月`,
      `七月`,
      `八月`,
      `九月`,
      `十月`,
      `十一月`,
      `十二月`
    ]
  },
  /* German (Germany) */
  'de_de-DE': {
    days: [
      `Sonntag`,
      `Montag`,
      `Dienstag`,
      `Mittwoch`,
      `Donnerstag`,
      `Freitag`,
      `Samstag`
    ],
    months: [
      `Januar`,
      `Februar`,
      `März`,
      `April`,
      `Mai`,
      `Juni`,
      `Juli`,
      `August`,
      `September`,
      `Oktober`,
      `November`,
      `Dezember`
    ]
  },
  /* Dutch */
  'nl_nl-NL_nl-BE': {
    days: [
      `Zondag`,
      `Maandag`,
      `Dinsdag`,
      `Woensdag`,
      `Donderdag`,
      `Vrijdag`,
      `Zaterdag`
    ],
    months: [
      `Januari`,
      `Februari`,
      `Maart`,
      `April`,
      `Mei`,
      `Juni`,
      `Juli`,
      `Augustus`,
      `September`,
      `Oktober`,
      `November`,
      `December`
    ],
    today: `Vandaag`,
    format: `D/M/Y`
  },
  'test_test-TEST': {
    days: [
      `Foo`,
      `Mon`,
      `Tue`,
      `Wed`,
      `Thu`,
      `Fri`,
      `Sat`
    ],
    months: [
      `Foo`,
      `February`,
      `March`,
      `April`,
      `May`,
      `June`,
      `July`,
      `August`,
      `September`,
      `October`,
      `November`,
      `December`
    ]
  }
};

export default locales;
<|endoftext|>