From 3ee7a6085330960c666b9b089f377b5f35893daa Mon Sep 17 00:00:00 2001 From: Matthew Riley MacPherson Date: Tue, 20 Dec 2016 15:34:07 +0000 Subject: [PATCH] Add support for i18n dates (fix #1395) Also makes our existing date output in AddonDetails pretty. --- .eslintignore | 1 + bin/build-locales | 35 ++++- package.json | 2 + src/amo/components/AddonMoreInfo.js | 11 +- src/core/client/base.js | 14 +- src/core/i18n/translate.js | 1 - src/core/i18n/utils.js | 25 ++++ src/core/server/base.js | 20 ++- test-runner.js | 5 +- tests/client/core/i18n/test_utils.js | 52 ++++++++ tests/client/helpers.js | 2 + webpack.dev.config.babel.js | 4 +- webpack.prod.config.babel.js | 4 +- yarn.lock | 184 +++++++++++++++------------ 14 files changed, 260 insertions(+), 100 deletions(-) diff --git a/.eslintignore b/.eslintignore index 2bbbcd3751..4b6ff67bf2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ server.babel.js dist coverage +src/locale diff --git a/bin/build-locales b/bin/build-locales index f674826553..2acfe71bcb 100755 --- a/bin/build-locales +++ b/bin/build-locales @@ -12,6 +12,7 @@ const po2json = require('po2json'); const glob = require('glob'); const shelljs = require('shelljs'); const chalk = require('chalk'); +const toSource = require('tosource'); const appName = config.get('appName'); @@ -30,7 +31,7 @@ poFiles.forEach((pofile) => { const subdir = path.dirname(dir); const locale = path.basename(subdir); const stem = path.basename(pofile, '.po'); - const jsonfile = path.join(dest, locale, `${stem}.json`); + const jsonfile = path.join(dest, locale, `${stem}.js`); shelljs.mkdir('-p', path.join(dest, locale)); const json = po2json.parseFileSync(pofile, { @@ -39,5 +40,35 @@ poFiles.forEach((pofile) => { format: 'jed1.x', fuzzy: config.get('po2jsonFuzzyOutput'), }); - fs.writeFileSync(jsonfile, json); + const localeObject = JSON.parse(json); + + // Add the moment locale JS into our locale file, if one is available and + // we're building for AMO (which is the only app that uses moment right + // now). + if (appName === 'amo') { + var defineLocale = null; + try { + const momentLocale = locale.replace('_', '-').toLowerCase(); + // We're using `new Function()` here to create a function out of the + // raw code in this file; this function won't be executed but will be + // written out by `toSource()` so that it can be used later (at runtime, + // by moment). + defineLocale = new Function( + fs.readFileSync( + `./node_modules/moment/locale/${momentLocale}.js`, 'utf8' + ) + ); + } catch (e) { + // We ignore missing locale errors for en_US as its moment's default + // locale so we don't need to provide a translation. + if (locale !== 'en_US') { + console.info(oneLine`No moment i18n available for ${locale}; + consider adding one or creating a mapping.`); + } + } + + localeObject._momentDefineLocale = defineLocale; + } + + fs.writeFileSync(jsonfile, `module.exports = ${toSource(localeObject)}`); }); diff --git a/package.json b/package.json index 5d597589d9..93a43f7af0 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "isomorphic-fetch": "2.2.1", "jed": "1.1.1", "jsdom": "9.9.1", + "moment": "2.17.1", "mozilla-tabzilla": "0.5.1", "normalize.css": "5.0.0", "normalizr": "2.3.0", @@ -254,6 +255,7 @@ "supertest": "2.0.1", "supertest-as-promised": "4.0.2", "svg-url-loader": "1.1.0", + "tosource": "1.0.0", "webpack": "1.14.0", "webpack-dev-middleware": "1.9.0", "webpack-dev-server": "1.16.2", diff --git a/src/amo/components/AddonMoreInfo.js b/src/amo/components/AddonMoreInfo.js index a66da9adb5..98b82e9636 100644 --- a/src/amo/components/AddonMoreInfo.js +++ b/src/amo/components/AddonMoreInfo.js @@ -32,7 +32,16 @@ export class AddonMoreInfoBase extends React.Component { {addon.current_version.version}
{i18n.gettext('Last updated')}
-
{addon.last_updated}
+
+ {i18n.sprintf( + // L10n: This will output, in English: + // "2 months ago (Dec 12 2016)" + i18n.gettext('%(timeFromNow)s (%(date)s)'), { + timeFromNow: i18n.moment(addon.last_updated).fromNow(), + date: i18n.moment(addon.last_updated).format('ll'), + } + )} +
{addon.current_version.license ? (
{ this.licenseHeader = ref; }}> {i18n.gettext('License')} diff --git a/src/core/client/base.js b/src/core/client/base.js index fea09528b0..5f96836cd7 100644 --- a/src/core/client/base.js +++ b/src/core/client/base.js @@ -2,7 +2,6 @@ import 'babel-polyfill'; import config from 'config'; -import Jed from 'jed'; import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; @@ -10,7 +9,7 @@ import { applyRouterMiddleware, Router, browserHistory } from 'react-router'; import { ReduxAsyncConnect } from 'redux-connect'; import useScroll from 'react-router-scroll/lib/useScroll'; -import { langToLocale, sanitizeLanguage } from 'core/i18n/utils'; +import { langToLocale, makeI18n, sanitizeLanguage } from 'core/i18n/utils'; import I18nProvider from 'core/i18n/Provider'; import log from 'core/logger'; @@ -24,8 +23,8 @@ export default function makeClient(routes, createStore) { const locale = langToLocale(lang); const appName = config.get('appName'); - function renderApp(jedData) { - const i18n = new Jed(jedData); + function renderApp(i18nData) { + const i18n = makeI18n(i18nData); if (initialStateContainer) { try { @@ -44,7 +43,10 @@ export default function makeClient(routes, createStore) { ), }); - const middleware = applyRouterMiddleware(useScroll(), useReduxAsyncConnect()); + const middleware = applyRouterMiddleware( + useScroll(), + useReduxAsyncConnect(), + ); render( @@ -62,7 +64,7 @@ export default function makeClient(routes, createStore) { try { if (locale !== langToLocale(config.get('defaultLang'))) { // eslint-disable-next-line max-len, global-require, import/no-dynamic-require - require(`bundle?name=[name]-i18n-[folder]!json!../../locale/${locale}/${appName}.json`)(renderApp); + require(`bundle?name=[name]-i18n-[folder]!../../locale/${locale}/${appName}.js`)(renderApp); } else { renderApp({}); } diff --git a/src/core/i18n/translate.js b/src/core/i18n/translate.js index f0b770277c..198017e2cc 100644 --- a/src/core/i18n/translate.js +++ b/src/core/i18n/translate.js @@ -5,7 +5,6 @@ function getDisplayName(component) { return component.displayName || component.name || 'Component'; } - export default function translate(options = {}) { const { withRef = false } = options; diff --git a/src/core/i18n/utils.js b/src/core/i18n/utils.js index dfa25c09f2..6b55495cf1 100644 --- a/src/core/i18n/utils.js +++ b/src/core/i18n/utils.js @@ -1,4 +1,6 @@ import config from 'config'; +import Jed from 'jed'; +import moment from 'moment'; import log from 'core/logger'; @@ -173,3 +175,26 @@ export function getLanguage({ lang, acceptLanguage } = {}) { // - normalization e.g: en-us -> en-US. return { lang: sanitizeLanguage(userLang), isLangFromHeader }; } + +// moment uses locales like "en-gb" whereas we use "en_GB". +export function makeMomentLocale(locale) { + return locale.replace('_', '-').toLowerCase(); +} + +// Create an i18n object with a translated moment object available we can +// use for translated dates across the app. +export function makeI18n(i18nData, _Jed = Jed) { + const i18n = new _Jed(i18nData); + + // This adds the correct moment locale for the active locale so we can get + // localised dates, times, etc. + if (i18n.options && typeof i18n.options._momentDefineLocale === 'function') { + i18n.options._momentDefineLocale(); + moment.locale(makeMomentLocale(i18n.options.locale_data.messages[''].lang)); + } + + // We add a translated "moment" property to our `i18n` object + // to make translated date/time/etc. easy. + i18n.moment = moment; + return i18n; +} diff --git a/src/core/server/base.js b/src/core/server/base.js index 5e22b78e7c..1dcb7f537c 100644 --- a/src/core/server/base.js +++ b/src/core/server/base.js @@ -5,7 +5,6 @@ import 'babel-polyfill'; import config from 'config'; import Express from 'express'; import helmet from 'helmet'; -import Jed from 'jed'; import cookie from 'react-cookie'; import React from 'react'; import ReactDOM from 'react-dom/server'; @@ -19,7 +18,12 @@ import { prefixMiddleWare } from 'core/middleware'; import { convertBoolean } from 'core/utils'; import { setClientApp, setLang, setJWT } from 'core/actions'; import log from 'core/logger'; -import { getDirection, isValidLang, langToLocale } from 'core/i18n/utils'; +import { + getDirection, + isValidLang, + langToLocale, + makeI18n, +} from 'core/i18n/utils'; import I18nProvider from 'core/i18n/Provider'; import WebpackIsomorphicToolsConfig from './webpack-isomorphic-tools-config'; @@ -121,7 +125,9 @@ function baseServer(routes, createStore, { appInstanceName = appName } = {}) { webpackIsomorphicTools.refresh(); } - match({ routes, location: req.url }, (err, redirectLocation, renderProps) => { + match({ location: req.url, routes }, ( + err, redirectLocation, renderProps + ) => { cookie.plugToRequest(req, res); if (err) { @@ -182,17 +188,19 @@ function baseServer(routes, createStore, { appInstanceName = appName } = {}) { return loadOnServer({ ...renderProps, store }) .then(() => { // eslint-disable-next-line global-require - let jedData = {}; + let i18nData = {}; try { if (locale !== langToLocale(config.get('defaultLang'))) { // eslint-disable-next-line global-require, import/no-dynamic-require - jedData = require(`json!../../locale/${locale}/${appInstanceName}.json`); + i18nData = require( + `../../locale/${locale}/${appInstanceName}.js`); } } catch (e) { log.info(`Locale JSON not found or required for locale: "${locale}"`); log.info(`Falling back to default lang: "${config.get('defaultLang')}".`); } - const i18n = new Jed(jedData); + const i18n = makeI18n(i18nData); + const InitialComponent = ( diff --git a/test-runner.js b/test-runner.js index 1159fb72cc..1f36cc6880 100644 --- a/test-runner.js +++ b/test-runner.js @@ -5,8 +5,9 @@ require('./tests/client/init'); const testsContext = require.context('./tests/client/', true, /\.js$/); const componentsContext = require.context( - // This regex excludes server.js or server/*.js - './src/', true, /^(?:(?!server|config|client).)*\.js$/); + // This regex excludes everything in locale/**/*.js, server.js, and + // server/*.js + './src/', true, /^(?:(?!locale\/[A-Za-z_]{2,5}\/|server|config|client).)*\.js$/); testsContext.keys().forEach(testsContext); componentsContext.keys().forEach(componentsContext); diff --git a/tests/client/core/i18n/test_utils.js b/tests/client/core/i18n/test_utils.js index 4b971c8436..a385656716 100644 --- a/tests/client/core/i18n/test_utils.js +++ b/tests/client/core/i18n/test_utils.js @@ -1,7 +1,9 @@ import config from 'config'; +import moment from 'moment'; import * as utils from 'core/i18n/utils'; + const defaultLang = config.get('defaultLang'); @@ -387,4 +389,54 @@ describe('i18n utils', () => { assert.equal(result, undefined); }); }); + + describe('makeI18n', () => { + let FakeJed; + + before(() => { + FakeJed = class { + constructor(i18nData) { + return i18nData; + } + }; + }); + + beforeEach(() => { + // FIXME: Our moment is not immutable so we reset it before each test. + // This is annoying to work around because of the locale `require()`s + // and it only affects tests so it'd be nice to fix but doesn't break + // anything. + moment.locale('en'); + }); + + it('adds a localised moment to the i18n object', () => { + const i18nData = {}; + const i18n = utils.makeI18n(i18nData, FakeJed); + assert.ok(i18n.moment); + assert.typeOf(i18n.moment, 'function'); + }); + + it('tries to localise moment', () => { + const i18nData = { + options: { + _momentDefineLocale: sinon.stub(), + locale_data: { messages: { '': { lang: 'fr' } } }, + }, + }; + const i18n = utils.makeI18n(i18nData, FakeJed); + assert.equal(i18n.moment.locale(), 'fr'); + }); + + it('does not localise if _momentDefineLocale is not a function', () => { + const i18nData = { + options: { + _momentDefineLocale: null, + locale_data: { messages: { '': { lang: 'fr' } } }, + }, + }; + + const i18n = utils.makeI18n(i18nData, FakeJed); + assert.equal(i18n.moment.locale(), 'en'); + }); + }); }); diff --git a/tests/client/helpers.js b/tests/client/helpers.js index a09dd377ae..33fe24ed98 100644 --- a/tests/client/helpers.js +++ b/tests/client/helpers.js @@ -1,5 +1,6 @@ import base64url from 'base64url'; import { sprintf } from 'jed'; +import moment from 'moment'; import React from 'react'; import { createRenderer } from 'react-addons-test-utils'; @@ -77,6 +78,7 @@ export function getFakeI18nInst() { npgettext: sinon.stub(), dnpgettext: sinon.stub(), sprintf: sinon.spy(sprintf), + moment, }; } diff --git a/webpack.dev.config.babel.js b/webpack.dev.config.babel.js index a24bf3dcdf..9ad1e98a6e 100644 --- a/webpack.dev.config.babel.js +++ b/webpack.dev.config.babel.js @@ -107,10 +107,12 @@ export default Object.assign({}, webpackConfig, { }), // Replaces server config module with the subset clientConfig object. new webpack.NormalModuleReplacementPlugin(/config$/, 'core/client/config.js'), + // Prevent locales with moment require calls from crashing + new webpack.NormalModuleReplacementPlugin(/\.\.\/moment$/, 'moment'), // This allow us to exclude locales for other apps being built. new webpack.ContextReplacementPlugin( /locale$/, - new RegExp(`^\\.\\/.*?\\/${appName}\\.json$`) + new RegExp(`^\\.\\/.*?\\/${appName}\\.js$`) ), // Substitutes client only config. new webpack.NormalModuleReplacementPlugin(/core\/logger$/, 'core/client/logger.js'), diff --git a/webpack.prod.config.babel.js b/webpack.prod.config.babel.js index 49b54d2792..b2318a23c1 100644 --- a/webpack.prod.config.babel.js +++ b/webpack.prod.config.babel.js @@ -78,6 +78,8 @@ const settings = { }), // Replaces server config module with the subset clientConfig object. new webpack.NormalModuleReplacementPlugin(/config$/, 'core/client/config.js'), + // Prevent locales with moment require calls from crashing + new webpack.NormalModuleReplacementPlugin(/\.\.\/moment$/, 'moment'), // Substitutes client only config. new webpack.NormalModuleReplacementPlugin(/core\/logger$/, 'core/client/logger.js'), // Use the browser's window for window. @@ -85,7 +87,7 @@ const settings = { // This allow us to exclude locales for other apps being built. new webpack.ContextReplacementPlugin( /locale$/, - new RegExp(`^\\.\\/.*?\\/${appName}\\.json$`) + new RegExp(`^\\.\\/.*?\\/${appName}\\.js$`) ), new ExtractTextPlugin('[name]-[contenthash].css', { allChunks: true }), new SriStatsPlugin({ diff --git a/yarn.lock b/yarn.lock index f923edf692..2923356e89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -280,19 +280,19 @@ babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.20.0: esutils "^2.0.2" js-tokens "^2.0.0" -babel-core@6.20.0, babel-core@^6.18.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.20.0.tgz#ab0d7176d9dea434e66badadaf92237865eab1ec" +babel-core@6.21.0, babel-core@^6.18.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.21.0.tgz#75525480c21c803f826ef3867d22c19f080a3724" dependencies: babel-code-frame "^6.20.0" - babel-generator "^6.20.0" + babel-generator "^6.21.0" babel-helpers "^6.16.0" babel-messages "^6.8.0" babel-register "^6.18.0" babel-runtime "^6.20.0" babel-template "^6.16.0" - babel-traverse "^6.20.0" - babel-types "^6.20.0" + babel-traverse "^6.21.0" + babel-types "^6.21.0" babylon "^6.11.0" convert-source-map "^1.1.0" debug "^2.1.1" @@ -313,13 +313,13 @@ babel-eslint@7.1.0: babylon "^6.11.2" lodash.pickby "^4.6.0" -babel-generator@^6.18.0, babel-generator@^6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.20.0.tgz#fee63614e0449390103b3097f3f6a118016c6766" +babel-generator@^6.18.0, babel-generator@^6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.21.0.tgz#605f1269c489a1c75deeca7ea16d43d4656c8494" dependencies: babel-messages "^6.8.0" babel-runtime "^6.20.0" - babel-types "^6.20.0" + babel-types "^6.21.0" detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.2.0" @@ -919,23 +919,23 @@ babel-template@^6.14.0, babel-template@^6.15.0, babel-template@^6.16.0, babel-te babylon "^6.11.0" lodash "^4.2.0" -babel-traverse@^6.15.0, babel-traverse@^6.16.0, babel-traverse@^6.18.0, babel-traverse@^6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.20.0.tgz#5378d1a743e3d856e6a52289994100bbdfd9872a" +babel-traverse@^6.15.0, babel-traverse@^6.16.0, babel-traverse@^6.18.0, babel-traverse@^6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.21.0.tgz#69c6365804f1a4f69eb1213f85b00a818b8c21ad" dependencies: babel-code-frame "^6.20.0" babel-messages "^6.8.0" babel-runtime "^6.20.0" - babel-types "^6.20.0" + babel-types "^6.21.0" babylon "^6.11.0" debug "^2.2.0" globals "^9.0.0" invariant "^2.2.0" lodash "^4.2.0" -babel-types@^6.13.0, babel-types@^6.15.0, babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.20.0, babel-types@^6.8.0, babel-types@^6.9.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.20.0.tgz#3869ecb98459533b37df809886b3f7f3b08d2baa" +babel-types@^6.13.0, babel-types@^6.15.0, babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.21.0, babel-types@^6.8.0, babel-types@^6.9.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.21.0.tgz#314b92168891ef6d3806b7f7a917fdf87c11a4b2" dependencies: babel-runtime "^6.20.0" esutils "^2.0.2" @@ -1278,7 +1278,7 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" -classnames@2.2.5: +classnames@2.2.5, classnames@^2.2.3: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" @@ -1569,7 +1569,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -cosmiconfig@^2.0.0, cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: +cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.1.tgz#817f2c2039347a1e9bf7d090c0923e53f749ca82" dependencies: @@ -2438,11 +2438,13 @@ fbjs@^0.8.1, fbjs@^0.8.4: promise "^7.1.1" ua-parser-js "^0.7.9" -fetch-mock@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.6.0.tgz#afdde7c5ef0d95e71cc35803e325d23d471d1cab" +fetch-mock@5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.8.0.tgz#1e80df8be828afc6879621ef662da70172790a08" dependencies: + glob-to-regexp "^0.3.0" node-fetch "^1.3.3" + path-to-regexp "^1.7.0" figures@^1.3.5: version "1.7.0" @@ -2718,6 +2720,10 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + glob@7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95" @@ -3036,14 +3042,10 @@ https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" -iconv-lite@0.4.13: +iconv-lite@0.4.13, iconv-lite@^0.4.13, iconv-lite@~0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" -iconv-lite@^0.4.13, iconv-lite@~0.4.13: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - icss-replace-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5" @@ -3406,9 +3408,9 @@ jsbn@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd" -jsdom@9.8.3: - version "9.8.3" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.8.3.tgz#fde29c109c32a1131e0b6c65914e64198f97c370" +jsdom@9.9.1: + version "9.9.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.9.1.tgz#84f3972ad394ab963233af8725211bce4d01bfd5" dependencies: abab "^1.0.0" acorn "^2.4.0" @@ -3420,7 +3422,7 @@ jsdom@9.8.3: escodegen "^1.6.1" html-encoding-sniffer "^1.0.1" iconv-lite "^0.4.13" - nwmatcher ">= 1.3.7 < 2.0.0" + nwmatcher ">= 1.3.9 < 2.0.0" parse5 "^1.5.1" request "^2.55.0" sax "^1.1.4" @@ -3428,7 +3430,7 @@ jsdom@9.8.3: tough-cookie "^2.3.1" webidl-conversions "^3.0.1" whatwg-encoding "^1.0.1" - whatwg-url "^3.0.0" + whatwg-url "^4.1.0" xml-name-validator ">= 2.0.1 < 3.0.0" jsesc@^1.3.0: @@ -3615,9 +3617,9 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -known-css-properties@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.0.5.tgz#33de5b8279010a72db917d33119e4c27c078490a" +known-css-properties@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.0.6.tgz#71a0b8fde1b6e3431c471efbc3d9733faebbcfbf" lazy-cache@^1.0.3: version "1.0.4" @@ -3675,7 +3677,7 @@ loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0. json5 "^0.5.0" object-assign "^4.0.1" -lodash-es@^4.2.0, lodash-es@^4.2.1: +lodash-es@^4.2.1: version "4.17.2" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.2.tgz#59011b585166e613eb9dd5fc256b2cd1a30f3712" @@ -3778,6 +3780,10 @@ lodash.merge@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" +lodash.mergewith@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" + lodash.pick@^4.2.1: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -4131,9 +4137,9 @@ node-pre-gyp@^0.6.29: tar "~2.2.1" tar-pack "~3.3.0" -node-sass@3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-3.13.1.tgz#7240fbbff2396304b4223527ed3020589c004fc2" +node-sass@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.1.1.tgz#dc3e27d25bd827b6276ea243be357c7c7cd07111" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -4144,6 +4150,7 @@ node-sass@3.13.1: in-publish "^2.0.0" lodash.assign "^4.2.0" lodash.clonedeep "^4.3.2" + lodash.mergewith "^4.6.0" meow "^3.7.0" mkdirp "^0.5.1" nan "^2.3.2" @@ -4151,6 +4158,7 @@ node-sass@3.13.1: npmlog "^4.0.0" request "^2.61.0" sass-graph "^2.1.1" + stdout-stream "^1.4.0" node-uuid@~1.4.7: version "1.4.7" @@ -4241,7 +4249,7 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -"nwmatcher@>= 1.3.7 < 2.0.0": +"nwmatcher@>= 1.3.9 < 2.0.0": version "1.3.9" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.3.9.tgz#8bab486ff7fa3dfd086656bbe8b17116d3692d2a" @@ -4424,6 +4432,12 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -4436,6 +4450,10 @@ pbkdf2-compat@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288" +photoswipe@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-4.1.1.tgz#41b756a2387e220c286598945503014bf622bba9" + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -4737,9 +4755,9 @@ postcss-reporter@^1.2.1, postcss-reporter@^1.3.3: log-symbols "^1.0.2" postcss "^5.0.0" -postcss-reporter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-2.0.0.tgz#d25e74ba7fce911e2aa72ec1ae592fade6ec3671" +postcss-reporter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-3.0.0.tgz#09ea0f37a444c5693878606e09b018ebeff7cf8f" dependencies: chalk "^1.0.0" lodash "^4.1.0" @@ -4984,6 +5002,14 @@ react-onclickoutside@5.7.1: dependencies: object-assign "^4.0.1" +react-photoswipe@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-photoswipe/-/react-photoswipe-1.2.0.tgz#e296e1150ed153bb2ddbc7222165291c13fe4e8c" + dependencies: + classnames "^2.2.3" + lodash.pick "^4.2.1" + photoswipe "^4.1.0" + react-proxy@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a" @@ -4991,17 +5017,7 @@ react-proxy@^1.1.7: lodash "^4.6.1" react-deep-force-update "^1.0.0" -react-redux@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.0.tgz#2693227b19876b6e0047f4b94eb4910f43b677ca" - dependencies: - hoist-non-react-statics "^1.0.3" - invariant "^2.0.0" - lodash "^4.2.0" - lodash-es "^4.2.0" - loose-envify "^1.1.0" - -react-redux@^4.0.0: +react-redux@4.4.6, react-redux@^4.0.0: version "4.4.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.6.tgz#4b9d32985307a11096a2dd61561980044fcc6209" dependencies: @@ -5430,9 +5446,9 @@ sass-graph@^2.1.1: lodash "^4.0.0" yargs "^4.7.1" -sass-loader@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-4.1.0.tgz#fd8604c5bf90001b173bb27540e8f2f5ed64602b" +sass-loader@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-4.1.1.tgz#79ef9468cf0bf646c29529e1f2cba6bd6e51c7bc" dependencies: async "^2.0.1" loader-utils "^0.2.15" @@ -5722,6 +5738,12 @@ sshpk@^1.7.0: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +stdout-stream@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" + dependencies: + readable-stream "^2.0.1" + stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" @@ -5839,19 +5861,19 @@ stylehacks@^2.3.0: text-table "^0.2.0" write-file-stdout "0.0.2" -stylelint-config-standard@14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-14.0.0.tgz#1164b79c3a1dd924ace1b756ad8ec00cbccb8132" +stylelint-config-standard@15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-15.0.1.tgz#f588e036bca6bb52391ea784198e773a9ca70efe" -stylelint@7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-7.6.0.tgz#ddeb06ccc95f72c119fcde5e85439fb7e9cde4df" +stylelint@7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-7.7.0.tgz#bf222c2a202c0b02d7834ad321fe0883d33cfde0" dependencies: autoprefixer "^6.0.0" balanced-match "^0.4.0" chalk "^1.1.1" colorguard "^1.2.0" - cosmiconfig "^2.0.0" + cosmiconfig "^2.1.1" doiuse "^2.4.1" execall "^1.0.0" get-stdin "^5.0.0" @@ -5859,7 +5881,7 @@ stylelint@7.6.0: globjoin "^0.1.4" html-tags "^1.1.1" ignore "^3.2.0" - known-css-properties "^0.0.5" + known-css-properties "^0.0.6" lodash "^4.0.0" log-symbols "^1.0.2" meow "^3.3.0" @@ -5868,7 +5890,7 @@ stylelint@7.6.0: postcss "^5.0.20" postcss-less "^0.14.0" postcss-media-query-parser "^0.2.0" - postcss-reporter "^2.0.0" + postcss-reporter "^3.0.0" postcss-resolve-nested-selector "^0.1.1" postcss-scss "^0.4.0" postcss-selector-parser "^2.1.1" @@ -5880,7 +5902,7 @@ stylelint@7.6.0: stylehacks "^2.3.0" sugarss "^0.2.0" svg-tags "^1.0.0" - table "^3.7.8" + table "^4.0.1" sugarss@^0.2.0: version "0.2.0" @@ -5991,6 +6013,17 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" +table@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + tapable@^0.1.8, tapable@~0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" @@ -6320,7 +6353,7 @@ webpack-custom-stats-patch@^0.3.0: dependencies: lodash.merge "^4.4.0" -webpack-dev-middleware@1.9.0: +webpack-dev-middleware@1.9.0, webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.4.0: version "1.9.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.9.0.tgz#a1c67a3dfd8a5c5d62740aa0babe61758b4c84aa" dependencies: @@ -6329,15 +6362,6 @@ webpack-dev-middleware@1.9.0: path-is-absolute "^1.0.0" range-parser "^1.0.3" -webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.4.0: - version "1.8.4" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.8.4.tgz#e8765c9122887ce9e3abd4cc9c3eb31b61e0948d" - dependencies: - memory-fs "~0.3.0" - mime "^1.3.4" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - webpack-dev-server@1.16.2: version "1.16.2" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-1.16.2.tgz#8bebc2c4ce1c45a15c72dd769d9ba08db306a793" @@ -6427,9 +6451,9 @@ whatwg-fetch@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" -whatwg-url@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-3.1.0.tgz#7bdcae490f921aef6451fb6739ec6bbd8e907bf6" +whatwg-url@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.1.1.tgz#567074923352de781e3500d64a86aa92a971b4a4" dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0"