osm-edit-bundle
Version:
Web tool for editing OpenStreetMap building indoors data
1,588 lines (1,354 loc) • 1.9 MB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var fileSaver = require('file-saver');
require('bootstrap/dist/css/bootstrap.css');
var bbox = _interopDefault(require('@turf/bbox'));
var Col = _interopDefault(require('react-bootstrap/Col'));
var Button = _interopDefault(require('react-bootstrap/Button'));
var util$3 = _interopDefault(require('util'));
var fs = _interopDefault(require('fs'));
var net = _interopDefault(require('net'));
var Modal = _interopDefault(require('react-bootstrap/Modal'));
var Close = _interopDefault(require('mdi-react/CloseIcon'));
var Delete = _interopDefault(require('mdi-react/DeleteIcon'));
var DeleteCircleOutline = _interopDefault(require('mdi-react/DeleteCircleOutlineIcon'));
var Container = _interopDefault(require('react-bootstrap/Container'));
var deepEqual$1 = _interopDefault(require('fast-deep-equal'));
require('array-flat-polyfill');
var L$1 = require('leaflet');
var L$1__default = _interopDefault(L$1);
var reactLeaflet = require('react-leaflet');
require('leaflet-editable');
require('leaflet-draw');
require('leaflet-geometryutil');
require('leaflet-textpath');
require('leaflet-polylinedecorator');
var area = _interopDefault(require('@turf/area'));
var GeoJSONValidation = _interopDefault(require('geojson-validation'));
var Mousetrap = _interopDefault(require('mousetrap'));
var pointOnFeature = _interopDefault(require('@turf/point-on-feature'));
var pointToLineDistance = _interopDefault(require('@turf/point-to-line-distance'));
var PubSub = _interopDefault(require('pubsub-js'));
var ButtonToolbar = _interopDefault(require('react-bootstrap/ButtonToolbar'));
var Dropdown = _interopDefault(require('react-bootstrap/Dropdown'));
var Layers = _interopDefault(require('mdi-react/LayersIcon'));
var Navbar = _interopDefault(require('react-bootstrap/Navbar'));
var Breadcrumb = _interopDefault(require('react-bootstrap/Breadcrumb'));
var Pencil = _interopDefault(require('mdi-react/PencilIcon'));
var FormControl = _interopDefault(require('react-bootstrap/FormControl'));
var InputGroup = _interopDefault(require('react-bootstrap/InputGroup'));
var Nominatim = _interopDefault(require('nominatim-browser'));
var Magnify = _interopDefault(require('mdi-react/MagnifyIcon'));
var Overlay = _interopDefault(require('react-bootstrap/Overlay'));
var Form = _interopDefault(require('react-bootstrap/Form'));
var ListGroup = _interopDefault(require('react-bootstrap/ListGroup'));
var Spinner = _interopDefault(require('react-bootstrap/Spinner'));
var Check = _interopDefault(require('mdi-react/CheckIcon'));
var Checkbox = _interopDefault(require('react-three-state-checkbox'));
var HelpCircle = _interopDefault(require('mdi-react/HelpCircleIcon'));
var Multiselect = _interopDefault(require('react-bootstrap-multiselect'));
var OverlayTrigger = _interopDefault(require('react-bootstrap/OverlayTrigger'));
var Popover = _interopDefault(require('react-bootstrap/Popover'));
require('react-bootstrap-multiselect/css/bootstrap-multiselect.css');
var Row = _interopDefault(require('react-bootstrap/Row'));
var InformationVariant = _interopDefault(require('mdi-react/InformationVariantIcon'));
var Plus = _interopDefault(require('mdi-react/PlusIcon'));
var DotsHorizontal = _interopDefault(require('mdi-react/DotsHorizontalIcon'));
var MapMarker = _interopDefault(require('mdi-react/MapMarkerIcon'));
var VectorLine = _interopDefault(require('mdi-react/VectorLineIcon'));
var VectorSquare = _interopDefault(require('mdi-react/VectorSquareIcon'));
var CloseCircle = _interopDefault(require('mdi-react/CloseCircleIcon'));
var ChevronLeft = _interopDefault(require('mdi-react/ChevronLeftIcon'));
var ChevronRight = _interopDefault(require('mdi-react/ChevronRightIcon'));
var MapMarkerMultiple = _interopDefault(require('mdi-react/MapMarkerMultipleIcon'));
var MapMarkerPlus = _interopDefault(require('mdi-react/MapMarkerPlusIcon'));
var Media = _interopDefault(require('react-bootstrap/Media'));
var History = _interopDefault(require('mdi-react/HistoryIcon'));
var Tab = _interopDefault(require('react-bootstrap/Tab'));
var Tabs = _interopDefault(require('react-bootstrap/Tabs'));
var Dropzone = _interopDefault(require('react-dropzone'));
var Eye = _interopDefault(require('mdi-react/EyeIcon'));
var EyeOff = _interopDefault(require('mdi-react/EyeOffIcon'));
var Hash = _interopDefault(require('object-hash'));
var ContentDuplicate = _interopDefault(require('mdi-react/ContentDuplicateIcon'));
var ShapePolygonPlus = _interopDefault(require('mdi-react/ShapePolygonPlusIcon'));
var SquareEditOutline = _interopDefault(require('mdi-react/SquareEditOutlineIcon'));
require('leaflet/dist/leaflet.css');
require('leaflet-hash');
var reactLeafletBing = require('react-leaflet-bing');
require('leaflet-toolbar');
require('leaflet-distortableimage');
require('leaflet-distortableimage/dist/leaflet.distortableimage.css');
var Alert = _interopDefault(require('mdi-react/AlertIcon'));
var OfficeBuilding = _interopDefault(require('mdi-react/OfficeBuildingIcon'));
var ButtonGroup = _interopDefault(require('react-bootstrap/ButtonGroup'));
var ContentSave = _interopDefault(require('mdi-react/ContentSaveIcon'));
var ArrowExpand = _interopDefault(require('mdi-react/ArrowExpandIcon'));
var AxisArrow = _interopDefault(require('mdi-react/AxisArrowIcon'));
var ContentCopy = _interopDefault(require('mdi-react/ContentCopyIcon'));
var ContentPaste = _interopDefault(require('mdi-react/ContentPasteIcon'));
var RotateRight = _interopDefault(require('mdi-react/RotateRightIcon'));
var ArrowExpandDown = _interopDefault(require('mdi-react/ArrowExpandDownIcon'));
var ArrowExpandUp = _interopDefault(require('mdi-react/ArrowExpandUpIcon'));
var Undo = _interopDefault(require('mdi-react/UndoIcon'));
var Redo = _interopDefault(require('mdi-react/RedoIcon'));
var booleanIntersects = _interopDefault(require('@turf/boolean-intersects'));
var url = _interopDefault(require('url'));
var punycode$1 = _interopDefault(require('punycode'));
var crypto = _interopDefault(require('crypto'));
var buffer = _interopDefault(require('buffer'));
var http = _interopDefault(require('http'));
var https = _interopDefault(require('https'));
var stream$1 = _interopDefault(require('stream'));
var zlib = _interopDefault(require('zlib'));
var querystring = _interopDefault(require('querystring'));
var assert = _interopDefault(require('assert'));
var path = _interopDefault(require('path'));
var tls = _interopDefault(require('tls'));
var events = _interopDefault(require('events'));
var OsmAuth = _interopDefault(require('osm-auth'));
var xml2js = require('xml2js');
var Fuse = _interopDefault(require('fuse.js'));
var bearing = _interopDefault(require('@turf/bearing'));
var helpers$1 = require('@turf/helpers');
var booleanContains = _interopDefault(require('@turf/boolean-contains'));
var booleanDisjoint = _interopDefault(require('@turf/boolean-disjoint'));
var booleanPointInPolygon = _interopDefault(require('@turf/boolean-point-in-polygon'));
var booleanPointOnLine = _interopDefault(require('@turf/boolean-point-on-line'));
var buffer$1 = _interopDefault(require('@turf/buffer'));
var centerOfMass = _interopDefault(require('@turf/center-of-mass'));
var meta = require('@turf/meta');
var intersect = _interopDefault(require('@turf/intersect'));
var nearestPoint = _interopDefault(require('@turf/nearest-point'));
var osmtogeojson = _interopDefault(require('osmtogeojson'));
var OsmRequest = _interopDefault(require('osm-request'));
var polygonToLine = _interopDefault(require('@turf/polygon-to-line'));
var transformTranslate = _interopDefault(require('@turf/transform-translate'));
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _createSuper(Derived) {
return function () {
var Super = _getPrototypeOf(Derived),
result;
if (_isNativeReflectConstruct()) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _superPropBase(object, property) {
while (!Object.prototype.hasOwnProperty.call(object, property)) {
object = _getPrototypeOf(object);
if (object === null) break;
}
return object;
}
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
var base = _superPropBase(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) {
return desc.get.call(receiver);
}
return desc.value;
};
}
return _get(target, property, receiver || target);
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(n);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _createForOfIteratorHelper(o) {
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) {
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var it,
normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = o[Symbol.iterator]();
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
// ulog - microscopically small universal logging library
// © 2017 by Stijn de Witt, some rights reserved
// License: CC-BY-4.0
function log(name){
return name
? (mods[name] ? mods[name] : (mods[name] = enhance({name:name}, log)))
: (log.debug ? log : enhance(log))
}
log.ulog = {version:'1.1.0'};
log.enable = function(str) {
var i, split = (str || '').split(/[\s,]+/);
for (i=0; i<split.length; i++) {
if (split[i]) {
str = split[i].replace(/\*/g, '.*?');
if (str[0] === '-') skipMods.push(new RegExp('^' + str.substr(1) + '$'));
else dbgMods.push(new RegExp('^' + str + '$'));
}
}
for (i in mods) patch(mods[i]);
};
log.enabled = function(name) {
var i;
for (i=0; i<skipMods.length; i++)
if (skipMods[i].test(name)) return
for (i=0; i<dbgMods.length; i++)
if (dbgMods[i].test(name)) {return true}
};
log.disable = log.enable.bind(log, '');
var LVL = {ERROR:1, WARN:2, INFO:3, LOG:4, DEBUG:5, TRACE:6},
names = Object.keys(LVL).map(function(x){return x.toLowerCase()}),
mods = {}, dbgMods = [], skipMods = [];
function enhance(o, parent, level) {
o.NONE = 0;
for (var key in LVL) {o[key] = LVL[key];}
Object.defineProperty(o, 'level', {
get: function(){return level !== undefined ? level : parent && parent.level},
set: function(n) {
if ((n === undefined) && parent) {level = undefined;}
else {
var lvl = n && (Number(n)!==Number(n) ? o[n.toUpperCase()] : Number(n));
if (lvl >= 0 && lvl <= 6) {level = lvl;}
}
patch(o);
if (!parent) {for (mod in mods) {patch(mods[mod]);}}
}
});
patch(o);
o.dir = bnd('dir') || nop;
o.table = bnd('table') || nop;
o.time = bnd('time') || nop;
o.timeEnd = bnd('timeEnd') || nop;
// makes Node behave like browsers
o.assert = typeof window == 'object' && bnd('assert') || function(){
var a=[].concat.apply([], arguments), ok=a.shift();
if (!ok) {o.error.apply(o, a);}
};
return o
}
function patch(o) {
var lvl = Math.max(o.name && log.enabled(o.name) && o.DEBUG || o.level, o.level);
for (var i=0,name; name=names[i]; i++) {
o[name] = lvl <= i
? nop
: (
bnd(name) ||
(typeof print == 'function' && print) ||
nop
);
}
}
function bnd(n,c){return (c = log.con()) && (c[n]||c.log).bind(c)}
function nop(){}
var ulog = log;
var level, debug;
if (process.env.LOG ) {level = process.env.LOG;}
if (process.env.DEBUG) {debug = process.env.DEBUG;}
var fd = process.env.DEBUG_FD && parseInt(process.env.DEBUG_FD, 10),
c = console;
if (typeof fd == 'number') {
var util = util$3,
stream = createWritableStdioStream(fd),
logger = function(){stream.write(util.format.apply(this, arguments) + '\n');};
c = {error:logger, warn:logger, info:logger, log:logger, trace:logger};
}
ulog.con = function(){return c};
if (debug) {ulog.enable(debug);}
var node = ulog();
ulog.level = level || ulog.INFO;
// SEE https://github.com/visionmedia/debug/blob/2.2.0/node.js#L144
function createWritableStdioStream (fd) {
var stream;
var tty_wrap = process.binding('tty_wrap');
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
stream = new tty.WriteStream(fd);
stream._type = 'tty';
// Hack to have stream not keep the event loop alive.
// See https://github.com/joyent/node/issues/1726
if (stream._handle && stream._handle.unref) {
stream._handle.unref();
}
break;
case 'FILE':
var fs$1 = fs;
stream = new fs$1.SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
case 'PIPE':
case 'TCP':
var net$1 = net;
stream = new net$1.Socket({
fd: fd,
readable: false,
writable: true
});
// FIXME Should probably have an option in net.Socket to create a
// stream from an existing fd which is writable only. But for now
// we'll just add this hack and set the `readable` member to false.
// Test: ./node test/fixtures/echo.js < /etc/passwd
stream.readable = false;
stream.read = null;
stream._type = 'pipe';
// FIXME Hack to have stream not keep the event loop alive.
// See https://github.com/joyent/node/issues/1726
if (stream._handle && stream._handle.unref) {
stream._handle.unref();
}
break;
default:
// Probably an error on in uv_guess_handle()
throw new Error('Implement me. Unknown stream file type!');
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}
var log$1 = function(name, silent) {
if (silent) node.level = node.NONE;
return node(name);
};
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
exports: {},
require: function (path, base) {
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
}
}, fn(module, module.exports), module.exports;
}
function getCjsExportFromNamespace (n) {
return n && n['default'] || n;
}
function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
var i18n = createCommonjsModule(function (module) {
(function (root, factory) {
if ( module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(root);
} else {
// Browser globals (root is window)
root.I18n = factory(root);
}
}(commonjsGlobal, function(global) {
// Use previously defined object if exists in current scope
var I18n = global && global.I18n || {};
// Just cache the Array#slice function.
var slice = Array.prototype.slice;
// Apply number padding.
var padding = function(number) {
return ("0" + number.toString()).substr(-2);
};
// Improved toFixed number rounding function with support for unprecise floating points
// JavaScript's standard toFixed function does not round certain numbers correctly (for example 0.105 with precision 2).
var toFixed = function(number, precision) {
return decimalAdjust('round', number, -precision).toFixed(precision);
};
// Is a given variable an object?
// Borrowed from Underscore.js
var isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object'
};
var isFunction = function(func) {
var type = typeof func;
return type === 'function'
};
// Check if value is different than undefined and null;
var isSet = function(value) {
return typeof(value) !== 'undefined' && value !== null;
};
// Is a given value an array?
// Borrowed from Underscore.js
var isArray = function(val) {
if (Array.isArray) {
return Array.isArray(val);
}
return Object.prototype.toString.call(val) === '[object Array]';
};
var isString = function(val) {
return typeof val === 'string' || Object.prototype.toString.call(val) === '[object String]';
};
var isNumber = function(val) {
return typeof val === 'number' || Object.prototype.toString.call(val) === '[object Number]';
};
var isBoolean = function(val) {
return val === true || val === false;
};
var isNull = function(val) {
return val === null;
};
var decimalAdjust = function(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
};
var lazyEvaluate = function(message, scope) {
if (isFunction(message)) {
return message(scope);
} else {
return message;
}
};
var merge = function (dest, obj) {
var key, value;
for (key in obj) if (obj.hasOwnProperty(key)) {
value = obj[key];
if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value) || isNull(value)) {
dest[key] = value;
} else {
if (dest[key] == null) dest[key] = {};
merge(dest[key], value);
}
}
return dest;
};
// Set default days/months translations.
var DATE = {
day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
, abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
, month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
, abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
, meridian: ["AM", "PM"]
};
// Set default number format.
var NUMBER_FORMAT = {
precision: 3
, separator: "."
, delimiter: ","
, strip_insignificant_zeros: false
};
// Set default currency format.
var CURRENCY_FORMAT = {
unit: "$"
, precision: 2
, format: "%u%n"
, sign_first: true
, delimiter: ","
, separator: "."
};
// Set default percentage format.
var PERCENTAGE_FORMAT = {
unit: "%"
, precision: 3
, format: "%n%u"
, separator: "."
, delimiter: ""
};
// Set default size units.
var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"];
// Other default options
var DEFAULT_OPTIONS = {
// Set default locale. This locale will be used when fallback is enabled and
// the translation doesn't exist in a particular locale.
defaultLocale: "en"
// Set the current locale to `en`.
, locale: "en"
// Set the translation key separator.
, defaultSeparator: "."
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
, placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm
// Set if engine should fallback to the default locale when a translation
// is missing.
, fallbacks: false
// Set the default translation object.
, translations: {}
// Set missing translation behavior. 'message' will display a message
// that the translation is missing, 'guess' will try to guess the string
, missingBehaviour: 'message'
// if you use missingBehaviour with 'message', but want to know that the
// string is actually missing for testing purposes, you can prefix the
// guessed string by setting the value here. By default, no prefix!
, missingTranslationPrefix: ''
};
// Set default locale. This locale will be used when fallback is enabled and
// the translation doesn't exist in a particular locale.
I18n.reset = function() {
var key;
for (key in DEFAULT_OPTIONS) {
this[key] = DEFAULT_OPTIONS[key];
}
};
// Much like `reset`, but only assign options if not already assigned
I18n.initializeOptions = function() {
var key;
for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) {
this[key] = DEFAULT_OPTIONS[key];
}
};
I18n.initializeOptions();
// Return a list of all locales that must be tried before returning the
// missing translation message. By default, this will consider the inline option,
// current locale and fallback locale.
//
// I18n.locales.get("de-DE");
// // ["de-DE", "de", "en"]
//
// You can define custom rules for any locale. Just make sure you return a array
// containing all locales.
//
// // Default the Wookie locale to English.
// I18n.locales["wk"] = function(locale) {
// return ["en"];
// };
//
I18n.locales = {};
// Retrieve locales based on inline locale, current locale or default to
// I18n's detection.
I18n.locales.get = function(locale) {
var result = this[locale] || this[I18n.locale] || this["default"];
if (isFunction(result)) {
result = result(locale);
}
if (isArray(result) === false) {
result = [result];
}
return result;
};
// The default locale list.
I18n.locales["default"] = function(locale) {
var locales = []
, list = []
;
// Handle the inline locale option that can be provided to
// the `I18n.t` options.
if (locale) {
locales.push(locale);
}
// Add the current locale to the list.
if (!locale && I18n.locale) {
locales.push(I18n.locale);
}
// Add the default locale if fallback strategy is enabled.
if (I18n.fallbacks && I18n.defaultLocale) {
locales.push(I18n.defaultLocale);
}
// Locale code format 1:
// According to RFC4646 (http://www.ietf.org/rfc/rfc4646.txt)
// language codes for Traditional Chinese should be `zh-Hant`
//
// But due to backward compatibility
// We use older version of IETF language tag
// @see http://www.w3.org/TR/html401/struct/dirlang.html
// @see http://en.wikipedia.org/wiki/IETF_language_tag
//
// Format: `language-code = primary-code ( "-" subcode )*`
//
// primary-code uses ISO639-1
// @see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
// @see http://www.iso.org/iso/home/standards/language_codes.htm
//
// subcode uses ISO 3166-1 alpha-2
// @see http://en.wikipedia.org/wiki/ISO_3166
// @see http://www.iso.org/iso/country_codes.htm
//
// @note
// subcode can be in upper case or lower case
// defining it in upper case is a convention only
// Locale code format 2:
// Format: `code = primary-code ( "-" region-code )*`
// primary-code uses ISO 639-1
// script-code uses ISO 15924
// region-code uses ISO 3166-1 alpha-2
// Example: zh-Hant-TW, en-HK, zh-Hant-CN
//
// It is similar to RFC4646 (or actually the same),
// but seems to be limited to language, script, region
// Compute each locale with its country code.
// So this will return an array containing
// `de-DE` and `de`
// or
// `zh-hans-tw`, `zh-hans`, `zh`
// locales.
locales.forEach(function(locale) {
var localeParts = locale.split("-");
var firstFallback = null;
var secondFallback = null;
if (localeParts.length === 3) {
firstFallback = [
localeParts[0],
localeParts[1]
].join("-");
secondFallback = localeParts[0];
}
else if (localeParts.length === 2) {
firstFallback = localeParts[0];
}
if (list.indexOf(locale) === -1) {
list.push(locale);
}
if (! I18n.fallbacks) {
return;
}
[
firstFallback,
secondFallback
].forEach(function(nullableFallbackLocale) {
// We don't want null values
if (typeof nullableFallbackLocale === "undefined") { return; }
if (nullableFallbackLocale === null) { return; }
// We don't want duplicate values
//
// Comparing with `locale` first is faster than
// checking whether value's presence in the list
if (nullableFallbackLocale === locale) { return; }
if (list.indexOf(nullableFallbackLocale) !== -1) { return; }
list.push(nullableFallbackLocale);
});
});
// No locales set? English it is.
if (!locales.length) {
locales.push("en");
}
return list;
};
// Hold pluralization rules.
I18n.pluralization = {};
// Return the pluralizer for a specific locale.
// If no specify locale is found, then I18n's default will be used.
I18n.pluralization.get = function(locale) {
return this[locale] || this[I18n.locale] || this["default"];
};
// The default pluralizer rule.
// It detects the `zero`, `one`, and `other` scopes.
I18n.pluralization["default"] = function(count) {
switch (count) {
case 0: return ["zero", "other"];
case 1: return ["one"];
default: return ["other"];
}
};
// Return current locale. If no locale has been set, then
// the current locale will be the default locale.
I18n.currentLocale = function() {
return this.locale || this.defaultLocale;
};
// Check if value is different than undefined and null;
I18n.isSet = isSet;
// Find and process the translation using the provided scope and options.
// This is used internally by some functions and should not be used as an
// public API.
I18n.lookup = function(scope, options) {
options = options || {};
var locales = this.locales.get(options.locale).slice()
, locale
, scopes
, fullScope
, translations
;
fullScope = this.getFullScope(scope, options);
while (locales.length) {
locale = locales.shift();
scopes = fullScope.split(this.defaultSeparator);
translations = this.translations[locale];
if (!translations) {
continue;
}
while (scopes.length) {
translations = translations[scopes.shift()];
if (translations === undefined || translations === null) {
break;
}
}
if (translations !== undefined && translations !== null) {
return translations;
}
}
if (isSet(options.defaultValue)) {
return lazyEvaluate(options.defaultValue, scope);
}
};
// lookup pluralization rule key into translations
I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) {
var pluralizer = this.pluralization.get(locale)
, pluralizerKeys = pluralizer(count)
, pluralizerKey
, message;
if (isObject(translations)) {
while (pluralizerKeys.length) {
pluralizerKey = pluralizerKeys.shift();
if (isSet(translations[pluralizerKey])) {
message = translations[pluralizerKey];
break;
}
}
}
return message;
};
// Lookup dedicated to pluralization
I18n.pluralizationLookup = function(count, scope, options) {
options = options || {};
var locales = this.locales.get(options.locale).slice()
, locale
, scopes
, translations
, message
;
scope = this.getFullScope(scope, options);
while (locales.length) {
locale = locales.shift();
scopes = scope.split(this.defaultSeparator);
translations = this.translations[locale];
if (!translations) {
continue;
}
while (scopes.length) {
translations = translations[scopes.shift()];
if (!isObject(translations)) {
break;
}
if (scopes.length === 0) {
message = this.pluralizationLookupWithoutFallback(count, locale, translations);
}
}
if (typeof message !== "undefined" && message !== null) {
break;
}
}
if (typeof message === "undefined" || message === null) {
if (isSet(options.defaultValue)) {
if (isObject(options.defaultValue)) {
message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue);
} else {
message = options.defaultValue;
}
translations = options.defaultValue;
}
}
return { message: message, translations: translations };
};
// Rails changed the way the meridian is stored.
// It started with `date.meridian` returning an array,
// then it switched to `time.am` and `time.pm`.
// This function abstracts this difference and returns
// the correct meridian or the default value when none is provided.
I18n.meridian = function() {
var time = this.lookup("time");
var date = this.lookup("date");
if (time && time.am && time.pm) {
return [time.am, time.pm];
} else if (date && date.meridian) {
return date.meridian;
} else {
return DATE.meridian;
}
};
// Merge serveral hash options, checking if value is set before
// overwriting any value. The precedence is from left to right.
//
// I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
// #=> {name: "John Doe", role: "user"}
//
I18n.prepareOptions = function() {
var args = slice.call(arguments)
, options = {}
, subject
;
while (args.length) {
subject = args.shift();
if (typeof(subject) != "object") {
continue;
}
for (var attr in subject) {
if (!subject.hasOwnProperty(attr)) {
continue;
}
if (isSet(options[attr])) {
continue;
}
options[attr] = subject[attr];
}
}
return options;
};
// Generate a list of translation options for default fallbacks.
// `defaultValue` is also deleted from options as it is returned as part of
// the translationOptions array.
I18n.createTranslationOptions = function(scope, options) {
var translationOptions = [{scope: scope}];
// Defaults should be an array of hashes containing either
// fallback scopes or messages
if (isSet(options.defaults)) {
translationOptions = translationOptions.concat(options.defaults);
}
// Maintain support for defaultValue. Since it is always a message
// insert it in to the translation options as such.
if (isSet(options.defaultValue)) {
translationOptions.push({ message: options.defaultValue });
}
return translationOptions;
};
// Translate the given scope with the provided options.
I18n.translate = function(scope, options) {
options = options || {};
var translationOptions = this.createTranslationOptions(scope, options);
var translation;
var usedScope = scope;
var optionsWithoutDefault = this.prepareOptions(options);
delete optionsWithoutDefault.defaultValue;
// Iterate through the translation options until a translation
// or message is found.
var translationFound =
translationOptions.some(function(translationOption) {
if (isSet(translationOption.scope)) {
usedScope = translationOption.scope;
translation = this.lookup(usedScope, optionsWithoutDefault);
} else if (isSet(translationOption.message)) {
translation = lazyEvaluate(translationOption.message, scope);
}
if (translation !== undefined && translation !== null) {
return true;
}
}, this);
if (!translationFound) {
return this.missingTranslation(scope, options);
}
if (typeof(translation) === "string") {
translation = this.interpolate(translation, options);
} else if (isArray(translation)) {
translation = translation.map(function(t) {
return (typeof(t) === "string" ? this.interpolate(t, options) : t);
}, this);
} else if (isObject(translation) && isSet(options.count)) {
translation = this.pluralize(options.count, usedScope, options);
}
return translation;
};
// This function interpolates the all variables in the given message.
I18n.interpolate = function(message, options) {
if (message == null) {
return message;
}
options = options || {};
var matches = message.match(this.placeholder)
, placeholder
, value
, name
, regex
;
if (!matches) {
return message;
}
while (matches.length) {
placeholder = matches.shift();
name = placeholder.replace(this.placeholder, "$1");
if (isSet(options[name])) {
value = options[name].toString().replace(/\$/gm, "_#$#_");
} else if (name in options) {
value = this.nullPlaceholder(placeholder, message, options);
} else {
value = this.missingPlaceholder(placeholder, message, options);
}
regex = new RegExp(placeholder.replace(/{/gm, "\\{").replace(/}/gm, "\\}"));
message = message.replace(regex, value);
}
return message.replace(/_#\$#_/g, "$");
};
// Pluralize the given scope using the `count` value.
// The pluralized translation may have other placeholders,
// which will be retrieved from `options`.
I18n.pluralize = function(count, scope, options) {
options = this.prepareOptions({count: String(count)}, options);
var pluralizer, result;
result = this.pluralizationLookup(count, scope, options);
if (typeof result.translations === "undefined" || result.translations == null) {
return this.missingTranslation(scope, options);
}
if (typeof result.message !== "undefined" && result.message != null) {
return this.interpolate(result.message, options);
}
else {
pluralizer = this.pluralization.get(options.locale);
return this.missingTranslation(scope + '.' + pluralizer(count)[0], options);
}
};
// Return a missing translation message for the given parameters.
I18n.missingTranslation = function(scope, options) {
//guess intended string
if(this.missingBehaviour === 'guess'){
//get only the last portion of the scope
var s = scope.split('.').slice(-1)[0];
//replace underscore with space && camelcase with space and lowercase letter
return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') +
s.replace('_',' ').replace(/([a-z])([A-Z])/g,
function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} );
}
var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale();
var fullScope = this.getFullScope(scope, options);
var fullScopeWithLocale = [localeForTranslation, fullScope].join(this.defaultSeparator);
return '[missing "' + fullScopeWithLocale + '" translation]';
};
// Return a missing placeholder message for given parameters
I18n.missingPlaceholder = function(placeholder, message, options) {
return "[missing " + placeholder + " value]";
};
I18n.nullPlaceholder = function() {
return I18n.missingPlaceholder.apply(I18n, arguments);
};
// Format number using localization rules.
// The options will be retrieved from the `number.format` scope.
// If this isn't present, then the following options will be used:
//
// - `precision`: `3`
// - `separator`: `"."`
// - `delimiter`: `","`
// - `strip_insignificant_zeros`: `false`
//
// You can also override these options by providing the `options` argument.
//
I18n.toNumber = function(number, options) {
options = this.prepareOptions(
options
, this.lookup("number.format")
, NUMBER_FORMAT
);
var negative = number < 0
, string = toFixed(Math.abs(number), options.precision).toString()
, parts = string.split(".")
, precision
, buffer = []
, formattedNumber
, format = options.format || "%n"
, sign = negative ? "-" : ""
;
number = parts[0];
precision = parts[1];
while (number.length > 0) {
buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
number = number.substr(0, number.length -3);
}
formattedNumber = buffer.join(options.delimiter);
if (options.strip_insignificant_zeros && precision) {
precision = precision.replace(/0+$/, "");
}
if (options.precision > 0 && precision) {
formattedNumber += options.separator + precision;
}
if (options.sign_first) {
format = "%s" + format;
}
else {
format = format.replace("%n", "%s%n");
}
formattedNumber = format
.replace("%u", options.unit)
.replace("%n", formattedNumber)
.replace("%s", sign)
;
return formattedNumber;
};
// Format currency with localization rules.
// The options will be retrieved from the `number.currency.format` and
// `number.format` scopes, in that order.
//
// Any missing option will be retrieved from the `I18n.toNumber` defaults and
// the following options:
//
// - `unit`: `"$"`
// - `precision`: `2`
// - `format`: `"%u%n"`
// - `delimiter`: `","`
// - `separator`: `"."`
//
// You can also override these options by providing the `options` argument.
//
I18n.toCurrency = function(number, options) {
options = this.prepareOptions(
options
, this.lookup("number.currency.format")
, this.lookup("number.format")
, CURRENCY_FORMAT
);
return this.toNumber(number, options);
};
// Localize several values.
// You can provide the following scopes: `currency`, `number`, or `percentage`.
// If you provide a scope that matches the `/^(date|time)/` regular expression
// then the `value` will be converted by using the `I18n.toTime` function.
//
// It will default to the value's `toString` function.
//
I18n.localize = function(scope, value, options) {
options || (options = {});
switch (scope) {
case "currency":
return this.toCurrency(value);
case "number":
scope = this.lookup("number.format");
return this.toNumber(value, scope);
case "percentage":
return this.toPercentage(value);
default:
var localizedValue;
if (scope.match(/^(date|time)/)) {
localizedValue = this.toTime(scope, value);
} else {
localizedValue = value.toString();
}
return this.interpolate(localizedValue, options);
}
};
// Parse a given `date` string into a JavaScript Date object.
// This function is time zone aware.
//
// The following string formats are recognized:
//
// yyyy-mm-dd
// yyyy-mm-dd[ T]hh:mm::ss
// yyyy-mm-dd[ T]hh:mm::ss
// yyyy-mm-dd[ T]hh:mm::ssZ
// yyyy-mm-dd[ T]hh:mm::ss+0000
// yyyy-mm-dd[ T]hh:mm::ss+00:00
// yyyy-mm-dd[ T]hh:mm::ss.123Z
//
I18n.parseDate = function(date) {
var matches, convertedDate, fraction;
// A date input of `null` or `undefined` will be returned as-is
if (date == null) {
return date;
}
// we have a date, so just return it.
if (typeof(date) === "object") {
return date;
}
matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/);
if (matches) {
for (var i = 1; i <= 6; i++) {
matches[i] = parseInt(matches[i], 10) || 0;
}
// month starts on 0
matches[2] -= 1;
fraction = matches[7] ? 1000 * ("0" + matches[7]) : null;
if (matches[8]) {
convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction));
} else {
convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction);
}
} else if (typeof(date) == "number") {
// UNIX timestamp
convertedDate = new Date();
convertedDate.setTime(date);
} else if (date.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)) {
// This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by
// webkit/firefox, but not by IE, so we must parse it manually.
convertedDate = new Date();
convertedDate.setTime(Date.parse([
RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5
].join(" ")));
} else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
// a valid javascript format with timezone info
convertedDate = new Date();