UNPKG

osm-edit-bundle

Version:

Web tool for editing OpenStreetMap building indoors data

1,588 lines (1,354 loc) 1.9 MB
'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();