Add support for i18n dates (fix #1395)

Also makes our existing date output in AddonDetails pretty.
This commit is contained in:
Matthew Riley MacPherson 2016-12-20 15:34:07 +00:00
Родитель 6b957ac8ff
Коммит 3ee7a60853
14 изменённых файлов: 260 добавлений и 100 удалений

Просмотреть файл

@ -1,3 +1,4 @@
server.babel.js
dist
coverage
src/locale

Просмотреть файл

@ -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)}`);
});

Просмотреть файл

@ -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",

Просмотреть файл

@ -32,7 +32,16 @@ export class AddonMoreInfoBase extends React.Component {
{addon.current_version.version}
</dd>
<dt>{i18n.gettext('Last updated')}</dt>
<dd>{addon.last_updated}</dd>
<dd>
{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'),
}
)}
</dd>
{addon.current_version.license ? (
<dt ref={(ref) => { this.licenseHeader = ref; }}>
{i18n.gettext('License')}

Просмотреть файл

@ -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(
<I18nProvider i18n={i18n}>
@ -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({});
}

Просмотреть файл

@ -5,7 +5,6 @@ function getDisplayName(component) {
return component.displayName || component.name || 'Component';
}
export default function translate(options = {}) {
const { withRef = false } = options;

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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 = (
<I18nProvider i18n={i18n}>
<Provider store={store} key="provider">

Просмотреть файл

@ -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);

Просмотреть файл

@ -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');
});
});
});

Просмотреть файл

@ -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,
};
}

Просмотреть файл

@ -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'),

Просмотреть файл

@ -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({

184
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"