From a0e5dc4723f0e119fff03931a6017da07a014870 Mon Sep 17 00:00:00 2001 From: vjrj Date: Sat, 9 Dec 2017 17:54:18 +0100 Subject: [PATCH] i18n in server. Some fixes --- .meteor/packages | 1 + .meteor/versions | 1 + .../api/Subscriptions/server/publications.js | 13 +- imports/startup/client/i18n.js | 84 +---- imports/startup/common/i18n.js | 56 ++++ imports/startup/server/email.js | 78 ++++- imports/startup/server/i18n.js | 31 ++ imports/startup/server/index.js | 1 + .../components/SelectionMap/SelectionMap.js | 14 +- imports/ui/pages/FiresMap/FiresMap.js | 78 ++--- imports/ui/pages/Sandbox/Sandbox.js | 3 +- package-lock.json | 299 ++++++++++++++++++ package.json | 5 + 13 files changed, 543 insertions(+), 121 deletions(-) create mode 100644 imports/startup/common/i18n.js create mode 100644 imports/startup/server/i18n.js diff --git a/.meteor/packages b/.meteor/packages index 33ce009..2f876f3 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -45,3 +45,4 @@ natestrauser:publish-performant-counts maximum:server-transform mys:fonts # fonst in npm ostrio:mailer # mailer +ostrio:meteor-root diff --git a/.meteor/versions b/.meteor/versions index 7e88bbb..0508941 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -84,6 +84,7 @@ observe-sequence@1.0.16 ordered-dict@1.0.9 ostrio:cookies@2.1.3 ostrio:mailer@2.0.6 +ostrio:meteor-root@1.0.6 peerlibrary:assert@0.2.5 peerlibrary:fiber-utils@0.6.0 peerlibrary:reactive-mongo@0.1.1 diff --git a/imports/api/Subscriptions/server/publications.js b/imports/api/Subscriptions/server/publications.js index 508900f..6257cb5 100644 --- a/imports/api/Subscriptions/server/publications.js +++ b/imports/api/Subscriptions/server/publications.js @@ -4,22 +4,21 @@ import Perlin from 'loms.perlin'; Perlin.seed(Math.random()); -Meteor.publishTransformed('userSubsToFires', function() { - +Meteor.publishTransformed('userSubsToFires', function () { // https://en.wikipedia.org/wiki/Location_obfuscation // https://en.wikipedia.org/wiki/Decimal_degrees#Precision // https://gis.stackexchange.com/questions/27792/what-simple-effective-techniques-for-obfuscating-points-are-available - return Subscriptions.find().serverTransform(function(doc) { - var location = doc.location; + return Subscriptions.find().serverTransform(function (doc) { + var location = doc.location; /* doc.lat = location.lat; - * doc.lon = location.lon;*/ + * doc.lon = location.lon; */ if (location) { doc.lat = Math.round(location.lat * 10) / 10; doc.lon = Math.round(location.lon * 10) / 10; } // console.log(`[${doc.lat}, ${doc.lon}]`); - var noiseBase = Perlin.perlin2(doc.lat, doc.lon) - var noise = Math.abs(noiseBase/3); + var noiseBase = Perlin.perlin2(doc.lat, doc.lon); + var noise = Math.abs(noiseBase / 3); // console.log(`Noise ${noise}, abs: ${Math.abs(noise)}`); doc.lat += noise; doc.lon += noise; diff --git a/imports/startup/client/i18n.js b/imports/startup/client/i18n.js index 713bccc..3bd05a6 100644 --- a/imports/startup/client/i18n.js +++ b/imports/startup/client/i18n.js @@ -1,12 +1,11 @@ +/* global CookieConsent Intl */ import i18n from 'i18next'; import backend from 'i18next-xhr-backend'; import LngDetector from 'i18next-browser-languagedetector'; import Cache from 'i18next-localstorage-cache'; -import { Meteor } from 'meteor/meteor'; import { T9n } from 'meteor-accounts-t9n'; -import en from 'meteor-accounts-t9n/build/en'; -import es from 'meteor-accounts-t9n/build/es'; import moment from 'moment'; +import i18nOpts from '../common/i18n'; // Adapted from: https://github.com/appigram/ryfma-boilerplate/blob/44c1eabfb9928b5623afab36a23997969e5beb02/imports/startup/client/i18n.js @@ -21,7 +20,7 @@ const detectorOptions = { // cache user language on caches: ['localStorage', 'cookie'], - excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage) + excludeCacheFor: ['cimode'] // languages to not persist (cookie, localStorage) }; const cacheOptions = { @@ -32,78 +31,29 @@ const cacheOptions = { // expiration expirationTime: 7 * 24 * 60 * 60 * 1000, // language versions - versions: {}, + versions: {} }; -var backOpts = { - // path where resources get loaded from - loadPath: '/locales/{{lng}}/{{ns}}.json', - - // path to post missing resources - addPath: '/locales/{{lng}}/{{ns}}.missing.json', - - // jsonIndent to use when storing json files - jsonIndent: 2 +i18nOpts.cache = cacheOptions; +i18nOpts.detection = detectorOptions; +i18nOpts.react = { + wait: true + // https://react.i18next.com/components/i18next-instance.ht + /* bindI18n: 'languageChanged loaded', + bindStore: 'added removed', + nsMode: 'default' */ }; -T9N_LANGUAGES='es,en'; - -const forceDebug = false; -const shouldDebug = (forceDebug && !Meteor.isProduction); - i18n.use(backend) .use(LngDetector) .use(Cache) - .init({ - backend: backOpts, - lng: 'es', - //fallbackLng: 'es', - fallbackLng: { - 'en-US': ['en'], - 'en-GB': ['en'], - 'pt-BR': ['pt'], - 'default': ['es'] - }, - interpolation: { - escapeValue: false, // not needed for react!! - formatSeparator: ",", - format: function(value, format, lng) { - // https://www.i18next.com/formatting.html - // console.log(`Value: ${value} with format: ${format} to lang: ${lng}`); - if (format === 'uppercase') return value.toUpperCase(); - if (value instanceof Date) return moment(value).format(format); - if (format === 'number') return Intl.NumberFormat(lng).format(value); - return value; - } - }, - whitelist: false, - // whitelist: ['es', 'en'], // allowed languages - load: 'all', // es-ES -> es, en-US -> en - debug: shouldDebug, - ns: 'common', - defaultNS: 'common', - saveMissing: shouldDebug, // if true seems it's fails to getResourceBundle - saveMissingTo: 'es', - keySeparator: 'ß', - nsSeparator: 'ð', - pluralSeparator: 'đ', - cache: cacheOptions, - detection: detectorOptions, - react: { - wait: true, - // https://react.i18next.com/components/i18next-instance.ht - /* bindI18n: 'languageChanged loaded', - bindStore: 'added removed', - nsMode: 'default' */ - } - }, function(err, t) { + .init(i18nOpts, (err, t) => { // initialized and ready to if (err) { console.error(err); return; } - document.title = t("AppName"); - + document.title = t('AppName'); // Accounts translation // https://github.com/softwarerero/meteor-accounts-t9n // console.log("Language: " + i18n.language); @@ -111,11 +61,11 @@ i18n.use(backend) // console.log(T9n.get('error.accounts.User not found')); // cookies eu consent - var cookiesOpt = { + const cookiesOpt = { cookieTitle: t('Uso de Cookies'), cookieMessage: t('Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso'), /* cookieMessage: t('Uso de Cookies'), - cookieMessageImply: t('Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso'),*/ + cookieMessageImply: t('Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso'), */ showLink: false, position: 'bottom', linkText: 'Lee más', @@ -129,7 +79,7 @@ i18n.use(backend) CookieConsent.init(cookiesOpt); }); -i18n.on('languageChanged', function(lng) { +i18n.on('languageChanged', (lng) => { moment.locale(lng); T9n.setLanguage(lng); }); diff --git a/imports/startup/common/i18n.js b/imports/startup/common/i18n.js new file mode 100644 index 0000000..d54e263 --- /dev/null +++ b/imports/startup/common/i18n.js @@ -0,0 +1,56 @@ +import { Meteor } from 'meteor/meteor'; +import moment from 'moment'; +// Load the js langs +import es from 'meteor-accounts-t9n/build/es'; +import en from 'meteor-accounts-t9n/build/en'; + +var backOpts = { + // path where resources get loaded from + loadPath: '/locales/{{lng}}/{{ns}}.json', + + // path to post missing resources + addPath: '/locales/{{lng}}/{{ns}}.missing.json', + + // jsonIndent to use when storing json files + jsonIndent: 2 +}; + +const forceDebug = false; +const shouldDebug = (forceDebug && !Meteor.isProduction); + +var i18nOpts = { + backend: backOpts, + lng: 'es', + // fallbackLng: 'es', + fallbackLng: { + 'en-US': ['en'], + 'en-GB': ['en'], + 'pt-BR': ['pt'], + 'default': ['es'] + }, + interpolation: { + escapeValue: false, // not needed for react!! + formatSeparator: ',', + format: function (value, format, lng) { + // https://www.i18next.com/formatting.html + // console.log(`Value: ${value} with format: ${format} to lang: ${lng}`); + if (format === 'uppercase') return value.toUpperCase(); + if (value instanceof Date) return moment(value).format(format); + if (format === 'number') return Intl.NumberFormat(lng).format(value); + return value; + } + }, + whitelist: false, + // whitelist: ['es', 'en'], // allowed languages + load: 'all', // es-ES -> es, en-US -> en + debug: shouldDebug, + ns: 'common', + defaultNS: 'common', + saveMissing: shouldDebug, // if true seems it's fails to getResourceBundle + saveMissingTo: 'es', + keySeparator: 'ß', + nsSeparator: 'ð', + pluralSeparator: 'đ' +}; + +export default i18nOpts; diff --git a/imports/startup/server/email.js b/imports/startup/server/email.js index 377599c..c0728e6 100644 --- a/imports/startup/server/email.js +++ b/imports/startup/server/email.js @@ -1,3 +1,79 @@ import { Meteor } from 'meteor/meteor'; +import nodemailer from 'nodemailer'; +import { MailTime } from 'meteor/ostrio:mailer'; +import i18n from 'i18next'; -if (Meteor.isDevelopment) process.env.MAIL_URL = Meteor.settings.private.MAIL_URL; +// console.log(i18n.t('Inicio del mailer')); + +const transports = []; + +// First transport +const fstTransport = nodemailer.createTransport(Meteor.settings.private.MAIL_URL); +transports.push(fstTransport); +// console.log(fstTransport.options.auth.user); + +const db = Meteor.users.rawDatabase(); // new Mongo.Collection('__mailTimeQueue__').rawDatabase(); + +// Use __mailTimeQueue collection in any db +// db.getCollection("__mailTimeQueue__").count() + +// https://litmus.com/community/discussions/4633-is-there-a-reliable-1px-horizontal-rule-method +const hr = ` + + + +
`; + +const MailQueue = new MailTime({ + db, + type: 'server', + strategy: 'balancer', // Transports will be used in round robin chain + transports, + from(transport) { + // To pass spam-filters `from` field should be correctly set + // for each transport, check `transport` object for more options + return `"${i18n.t('AppName')}" <${transport.options.auth.user}>`; + }, + debug: true, + concatEmails: true, // Concatenate emails to the same addressee + concatSubject: `${i18n.t('Nuevas notificaciones de {{app}}', { app: i18n.t('AppName') })}`, + /* eslint-disable */ + concatDelimiter: hr + '

{{{subject}}}

', // Start each concatenated email with it's own subject + /* eslint-enable */ + // concatThrottling: 30, + template: MailTime.Template // Use default template +}); + +// A Client (not used yet) +// const MailQueueClient = new MailTime({ +// db, +// type: 'client', +// debug: true, +// strategy: 'balancer', // Transports will be used in round robin chain +// concatEmails: true // Concatenate emails to the same address +// }); + +export default function sendMail(opts, debug) { + if (debug) { + MailQueue.sendMail(opts, (err, info) => { if (err) { console.error(err); } else { console.log(info); } }); + } else { + MailQueue.sendMail(opts); + } +} + +if (Meteor.settings.private.testMailer) { + const emailOpts = { + to: Meteor.settings.private.testEmail, + userName: 'someone', + sendAt: new Date(), + subject: 'Some new notification', + text: 'Plain text message', + template: '{{appName}}

{{{subject}}}

{{{html}}}', + appName: i18n.t('AppName'), + html: '

Styled message

' + }; + sendMail(emailOpts, true); + sendMail(emailOpts, true); + sendMail(emailOpts, true); + sendMail(emailOpts, true); +} diff --git a/imports/startup/server/i18n.js b/imports/startup/server/i18n.js new file mode 100644 index 0000000..f2652b4 --- /dev/null +++ b/imports/startup/server/i18n.js @@ -0,0 +1,31 @@ +import { Meteor } from 'meteor/meteor'; +import i18n from 'i18next'; +import backend from 'i18next-sync-fs-backend'; +import i18nOpts from '../common/i18n'; +// import moment from 'moment'; +// import { T9n } from 'meteor-accounts-t9n'; + +i18nOpts.backend.loadPath = `${Meteor.absolutePath}/public${i18nOpts.backend.loadPath}`; +i18nOpts.backend.addPath = `${Meteor.absolutePath}/public${i18nOpts.backend.addPath}`; + +// console.log(i18nOpts.backend.loadPath); +i18nOpts.debug = false; +i18nOpts.saveMissing = true; +i18nOpts.initImmediate = false; + +i18n + .use(backend) + .init(i18nOpts, (err) => { + if (err) { + console.error(err); + } + }); + +/* export function setUserLang (lng) { + * moment.locale(lng); + * T9n.setLanguage(lng); + * } */ + +// console.log(i18n.t('Servidor arrancado')); + +export default i18n; diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 6c4282c..9ba16f8 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -1,3 +1,4 @@ +import './i18n'; import './accounts'; import './api'; import './fixtures'; diff --git a/imports/ui/components/SelectionMap/SelectionMap.js b/imports/ui/components/SelectionMap/SelectionMap.js index b8cbcd5..d424022 100644 --- a/imports/ui/components/SelectionMap/SelectionMap.js +++ b/imports/ui/components/SelectionMap/SelectionMap.js @@ -18,10 +18,10 @@ const positionIcon = new Leaflet.Icon({ /* shadowUrl: require('../public/marker-shadow.png'), */ iconSize: [50, 77], // size of the icon /* shadowSize: [50, 64], // size of the shadow */ - iconAnchor: [25, 82], // point of the icon which will correspond to marker's location + iconAnchor: [25, 82] // point of the icon which will correspond to marker's location /* shadowAnchor: [4, 62], // the same for the shadow * popupAnchor: [-3, -76]// point from which the popup should open relative to the iconAnchor*/ -}) +}); class SelectionMap extends Component { constructor(props) { @@ -45,11 +45,11 @@ class SelectionMap extends Component { } toggleDraggable = () => { - this.setState({ draggable: !this.state.draggable }) + this.setState({ draggable: !this.state.draggable }); } updatePosition = () => { - const { lat, lng } = this.refs.marker.leafletElement.getLatLng() + const { lat, lng } = this.refs.marker.leafletElement.getLatLng(); this.setState({ marker: [ lat, lng ], modified: true @@ -71,8 +71,8 @@ class SelectionMap extends Component { const map = this.getMap(); var options = { fill: 'fill', - showSubunits: true, - } + showSubunits: true + }; var graphicScale = L.control.graphicScale([options]).addTo(map); } @@ -129,7 +129,7 @@ class SelectionMap extends Component { } - ) + ); } } diff --git a/imports/ui/pages/FiresMap/FiresMap.js b/imports/ui/pages/FiresMap/FiresMap.js index e7afd88..303eadf 100644 --- a/imports/ui/pages/FiresMap/FiresMap.js +++ b/imports/ui/pages/FiresMap/FiresMap.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { Meteor } from 'meteor/meteor'; import { Trans, translate } from 'react-i18next'; import 'leaflet/dist/leaflet.css'; -import { Circle, CircleMarker, Map, Marker, Popup, TileLayer, PropTypes as MapPropTypes } from 'react-leaflet' +import { Circle, CircleMarker, Map, Marker, Popup, TileLayer, PropTypes as MapPropTypes } from 'react-leaflet'; import ActiveFiresCollection from '../../../api/ActiveFires/ActiveFires'; import FireAlertsCollection from '../../../api/FireAlerts/FireAlerts'; import UserSubsToFiresCollection from '../../../api/Subscriptions/Subscriptions'; @@ -12,7 +12,7 @@ import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/Center import { withTracker } from 'meteor/react-meteor-data'; import Loading from '../../components/Loading/Loading'; import './FiresMap.scss'; -import Leaflet from 'leaflet' +import Leaflet from 'leaflet'; import LGeo from 'leaflet-geodesy'; import union from '@turf/union'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.css'; @@ -36,7 +36,7 @@ const fireIcon = new Leaflet.Icon({ /* shadowUrl: require('../public/marker-shadow.png'), */ iconSize: [16, 24], // size of the icon /* shadowSize: [50, 64], // size of the shadow */ - iconAnchor: [8, 26], // point of the icon which will correspond to marker's location + iconAnchor: [8, 26] // point of the icon which will correspond to marker's location /* shadowAnchor: [4, 62], // the same for the shadow * popupAnchor: [-3, -76]// point from which the popup should open relative to the iconAnchor*/ }) @@ -46,7 +46,7 @@ const nFireIcon = new Leaflet.Icon({ /* shadowUrl: require('../public/marker-shadow.png'), */ iconSize: [16, 24], // size of the icon /* shadowSize: [50, 64], // size of the shadow */ - iconAnchor: [8, 26], // point of the icon which will correspond to marker's location + iconAnchor: [8, 26] // point of the icon which will correspond to marker's location /* shadowAnchor: [4, 62], // the same for the shadow * popupAnchor: [-3, -76]// point from which the popup should open relative to the iconAnchor*/ }) @@ -60,38 +60,38 @@ const MyPopupMarker = ({ children, lat, lon, nasa}) => ( -) +); const FireMark = ({ lat, lon, scan, nasa }) => ( -) +); /* Less acurate (1 pixel per fire) but faster */ const FirePixel = ({ lat, lon, nasa }) => ( -) +); MyPopupMarker.propTypes = { // https://github.com/PaulLeCam/react-leaflet/tree/master/src/propTypes children: MapPropTypes.children, lat: PropTypes.number.isRequired, lon: PropTypes.number.isRequired, - nasa: PropTypes.bool.isRequired, -} + nasa: PropTypes.bool.isRequired +}; FirePixel.propTypes = { lat: PropTypes.number.isRequired, lon: PropTypes.number.isRequired, - nasa: PropTypes.bool.isRequired, -} + nasa: PropTypes.bool.isRequired +}; FireMark.propTypes = { scan: PropTypes.number.isRequired, lat: PropTypes.number.isRequired, lon: PropTypes.number.isRequired, - nasa: PropTypes.bool.isRequired, -} + nasa: PropTypes.bool.isRequired +}; // Below this use only pixels const MAXZOOM = 7; @@ -99,9 +99,9 @@ const MAXZOOM = 7; const MyMarkersList = ({ markers }) => { const items = markers.map(({ key, ...props }) => ( - )) - return
{items}
-} + )); + return
{items}
; +}; const FireList = ({ fires, scale, useMarkers, nasa }) => { /* if (nasa) { @@ -112,20 +112,20 @@ const FireList = ({ fires, scale, useMarkers, nasa }) => { * }*/ const items = fires.map(({ _id, ...props }) => ( useMarkers && !scale? : - (!nasa && !scale)? :)) - return
{items}
-} + (!nasa && !scale)? :)); + return
{items}
; +}; MyMarkersList.propTypes = { - markers: PropTypes.array.isRequired, -} + markers: PropTypes.array.isRequired +}; const DEF_LAT = 35.159028; const DEF_LNG = -46.738057; const DEFAULT_VIEWPORT = { center: [DEF_LAT, DEF_LNG], // a point in the sea - zoom: 8, -} + zoom: 8 +}; class FiresMap extends React.Component { @@ -136,13 +136,13 @@ class FiresMap extends React.Component { modified: false, useMarkers: false, showSubsUnion: true - } + }; this.unionGroup = new L.LayerGroup(); } centerOnUserLocation = (viewport) => { this.onViewportChanged(viewport); - } + }; componentDidMount() { height.set(this.divElement.clientHeight); @@ -156,7 +156,9 @@ class FiresMap extends React.Component { lng.set(viewport.center[1]); self.state.viewport = viewport; self.state.modified = true; - self.showSubsUnion(self.state.showSubsUnion); + if (self.props.subsready && self.refs.fireMap) { + self.showSubsUnion(self.state.showSubsUnion); + } }, 2000); } @@ -192,17 +194,17 @@ class FiresMap extends React.Component { if (show) { // http://leafletjs.com/reference-1.2.0.html#path var copts = { - parts: 144, + parts: 144 }; UserSubsToFiresCollection.find().forEach( function(subs){ - var circle = LGeo.circle([subs.lat, subs.lon], subs.distance * 1000, copts) + var circle = LGeo.circle([subs.lat, subs.lon], subs.distance * 1000, copts); circle.addTo(unionGroup); }); this.union = unify(unionGroup.getLayers()); this.union.setStyle({ color: "#145A32", fillColor: "green", - fillOpacity: .1, + fillOpacity: .1 }); this.union.addTo(map); } @@ -213,15 +215,15 @@ class FiresMap extends React.Component { const map = this.getMap(); var options = { fill: 'fill', - showSubunits: true, - } + showSubunits: true + }; var graphicScale = L.control.graphicScale([options]).addTo(map); } render() { this.state.viewport = !this.state.modified && this.props.viewport && Array.isArray(this.props.viewport.center)? this.props.viewport: this.state.viewport; - if (this.props.subsready && this.refs['fireMap']) { + if (this.props.subsready && this.refs.fireMap) { // Show union of users this.showSubsUnion(this.state.showSubsUnion); }; @@ -240,12 +242,12 @@ class FiresMap extends React.Component {

Fuegos activos

-

+

{this.props.activefires.length === 0? No hay fuegos activos en esta zona del mapa. Hay un total de {{countTotal: this.props.activefirestotal}} fuegos activos detectados en todo el mundo.:En rojo, {{count: this.props.activefires.length}} fuegos activos en el mapa. Hay un total de {{countTotal: this.props.activefirestotal}} fuegos activos detectados en todo el mundo por la NASA. } -

-

En naranja, los fuegos notificados por nuestros usuarios/as recientemente.

+

+

En naranja, los fuegos notificados por nuestros usuarios/as recientemente.

this.showSubsUnion(e.target.checked)}> @@ -288,8 +290,8 @@ class FiresMap extends React.Component {
); - } -} + }; +}; const zoom = new ReactiveVar(8); const lat = new ReactiveVar(DEF_LAT); @@ -343,7 +345,7 @@ export default translate([], { wait: true }) (withTracker(() => { userSubs: UserSubsToFiresCollection.find().fetch(), viewport: { center: [lat.get(), lng.get()], // a point in the sea - zoom: zoom.get(), + zoom: zoom.get() } }; })(FiresMap)); diff --git a/imports/ui/pages/Sandbox/Sandbox.js b/imports/ui/pages/Sandbox/Sandbox.js index 314c869..535bb86 100644 --- a/imports/ui/pages/Sandbox/Sandbox.js +++ b/imports/ui/pages/Sandbox/Sandbox.js @@ -26,7 +26,8 @@ class Sandbox extends React.Component {
- ) + ); } } + export default translate([], { wait: true }) (Sandbox); diff --git a/package-lock.json b/package-lock.json index ba5e84e..96dada4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -228,6 +228,11 @@ "sprintf-js": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" } }, + "argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", + "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=" + }, "aria-query": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.0.tgz", @@ -2384,6 +2389,212 @@ "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "codecov": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-2.3.1.tgz", + "integrity": "sha1-fdqUXNWKH2CBAltbA+4Bou8g+G4=", + "requires": { + "argv": "0.0.2", + "request": "2.77.0", + "urlgrey": "0.4.4" + }, + "dependencies": { + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.16.1", + "pinkie-promise": "2.0.1" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "2.0.4" + } + }, + "qs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" + }, + "request": { + "version": "2.77.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.77.0.tgz", + "integrity": "sha1-KwDYIDDt7cyXCJ/6XYgQqcKqMUs=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "node-uuid": "1.4.8", + "oauth-sign": "0.8.2", + "qs": "6.3.2", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.4.3" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + } + } + }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", @@ -4415,6 +4626,19 @@ } } }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "requires": { + "is-property": "1.0.2" + } + }, "geojson-area": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/geojson-area/-/geojson-area-0.0.1.tgz", @@ -4726,6 +4950,50 @@ "resolved": "https://registry.npmjs.org/i18next-localstorage-cache/-/i18next-localstorage-cache-1.1.1.tgz", "integrity": "sha1-V1JWzDXoyy2IFI91R2b90tJLsbc=" }, + "i18next-sync-fs-backend": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/i18next-sync-fs-backend/-/i18next-sync-fs-backend-1.0.0.tgz", + "integrity": "sha512-fymETW6cOnpqf2tm14/CgpPn881SJYACD98lwvsflHKFPLEKm6S0e6atGCdQ37beizsiZJuGF02Bjx7yS/DAzg==", + "requires": { + "codecov": "2.3.1", + "js-yaml": "3.5.4", + "json5": "0.5.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "requires": { + "sprintf-js": "1.0.3" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + }, + "js-yaml": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.4.tgz", + "integrity": "sha1-9k8W3NeL65zoNhBo5zPr5HsHkXk=", + "requires": { + "argparse": "1.0.9", + "esprima": "2.7.3" + } + }, + "json5": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.0.tgz", + "integrity": "sha1-myBxWwJsvjd4/Xae3M2CLYMypbI=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + } + } + }, "i18next-xhr-backend": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-1.5.0.tgz", @@ -4985,6 +5253,17 @@ "is-extglob": "1.0.0" } }, + "is-my-json-valid": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", + "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -5036,6 +5315,11 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, "is-regex": { "version": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", @@ -6964,6 +7248,11 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -8471,6 +8760,11 @@ } } }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "nodemailer": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.4.1.tgz", @@ -11029,6 +11323,11 @@ "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz" } }, + "urlgrey": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", + "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=" + }, "util-deprecate": { "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" diff --git a/package.json b/package.json index c78877a..f3080d7 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "i18next": "^10.0.7", "i18next-browser-languagedetector": "^2.1.0", "i18next-localstorage-cache": "^1.1.1", + "i18next-sync-fs-backend": "^1.0.0", "i18next-xhr-backend": "^1.5.0", "immutability-helper": "^2.5.1", "indexof": "0.0.1", @@ -114,6 +115,10 @@ "error", "except-parens" ], + "comma-dangle": [ + "error", + "never" + ], "no-underscore-dangle": [ "error", {