Core POC support for polymer i18n (#227)

* Core POC support for polymer i18n

* Move translation from core.js to html

* Replace fetch with XHR

* Convert translation pipeline to gulp

* Convert from polyglot to Polymer localize

* Pass through missing keys for custom panels

* Store promise to be reused

* Use cacheFirst sw handler for translations

* Write full filenames to translationFingerprints

* Precache en translation

* Convert home-assistant-main to ES6 class

* Create a localization mixin

* Cleanup

* Add polymer tags to annotate for linter

* Rename fingerprints to translationMetadata

* Build translation native names into metadata

* Add language selection UI to sidebar

* Provide separate message namespace argument

* Store language/resources on hass object

* Store translationMetadata on hass

* Move language selector to config panel

* Temporarily hide language selector

* Small cleanups

* Use dynamic-align for more flexible layout

* Migrate to fetch API

* Only send change events for user selection events

* Update for new linting rules

* Migrate build_frontend changes
This commit is contained in:
Adam Mills 2017-10-25 21:12:23 -04:00 committed by Paulus Schoutsen
parent 7b61826134
commit 29fad98754
15 changed files with 554 additions and 15 deletions

View File

@ -9,6 +9,7 @@
"private": true,
"dependencies": {
"app-layout": "^2.0.0",
"app-localize-behavior": "PolymerElements/app-localize-behavior#~2.0.0",
"app-route": "PolymerElements/app-route#^2.0.0",
"app-storage": "^2.0.2",
"fecha": "~2.3.0",

View File

@ -30,7 +30,7 @@ function renamePanel(path) {
}
}
gulp.task('build', ['ru_all'], () => {
gulp.task('build', ['ru_all', 'build-translations'], () => {
const strategy = composeStrategies([
generateShellMergeStrategy(polymerConfig.shell),
stripImportsStrategy([

View File

@ -39,6 +39,7 @@ var staticFingerprinted = [
'mdi.html',
'core.js',
'compatibility.js',
'translations/en.json',
];
// These panels will always be registered inside HA and thus can
@ -62,9 +63,10 @@ gulp.task('gen-service-worker', () => {
// Create fingerprinted versions of our dependencies.
staticFingerprinted.forEach(fn => {
var parts = path.parse(fn);
var hash = md5(rootDir + '/' + parts.name + parts.ext);
var url = '/static/' + parts.name + '-' + hash + parts.ext;
var fpath = rootDir + '/' + parts.name + parts.ext;
var base = parts.dir.length > 0 ? parts.dir + '/' + parts.name : parts.name;
var hash = md5(rootDir + '/' + base + parts.ext);
var url = '/static/' + base + '-' + hash + parts.ext;
var fpath = rootDir + '/' + base + parts.ext;
dynamicUrlToDependencies[url] = [fpath];
});
@ -89,6 +91,10 @@ gulp.task('gen-service-worker', () => {
rootDir + '/fonts/roboto/Roboto-Bold.ttf',
rootDir + '/images/card_media_player_bg.png',
],
runtimeCaching: [{
urlPattern: /\/static\/translations\//,
handler: 'cacheFirst',
}],
stripPrefix: 'hass_frontend',
replacePrefix: 'static',
verbose: true,

111
gulp/tasks/translations.js Executable file
View File

@ -0,0 +1,111 @@
const path = require('path');
const gulp = require('gulp');
const foreach = require('gulp-foreach');
const hash = require('gulp-hash');
const insert = require('gulp-insert');
const merge = require('gulp-merge-json');
const minify = require('gulp-jsonminify');
const rename = require('gulp-rename');
const transform = require('gulp-json-transform');
const inDir = 'translations'
const outDir = 'build/translations';
const tasks = [];
function recursive_flatten (prefix, data) {
var output = {};
Object.keys(data).forEach(function (key) {
if (typeof(data[key]) === 'object') {
output = Object.assign({}, output, recursive_flatten(key + '.', data[key]));
} else {
output[prefix + key] = data[key];
}
});
return output
}
function flatten (data) {
return recursive_flatten('', data);
}
var taskName = 'build-translation-native-names';
gulp.task(taskName, function() {
return gulp.src(inDir + '/*.json')
.pipe(transform(function(data, file) {
// Look up the native name for each language and generate a json
// object with all available languages and native names
const lang = path.basename(file.relative, '.json');
return {[lang]: {nativeName: data.language[lang]}};
}))
.pipe(merge({
fileName: 'translationNativeNames.json',
}))
.pipe(gulp.dest('build-temp'));
});
tasks.push(taskName);
var taskName = 'build-merged-translations';
gulp.task(taskName, function () {
return gulp.src(inDir + '/*.json')
.pipe(foreach(function(stream, file) {
// For each language generate a merged json file. It begins with en.json as
// a failsafe for untranslated strings, and merges all parent tags into one
// file for each specific subtag
const tr = path.basename(file.history[0], '.json');
const subtags = tr.split('-');
const src = [inDir + '/en.json']; // Start with en as a fallback for missing translations
for (i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join('-');
src.push(inDir + '/' + lang + '.json');
}
return gulp.src(src)
.pipe(merge({
fileName: tr + '.json',
}))
.pipe(transform(function(data, file) {
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
}))
.pipe(minify())
.pipe(gulp.dest(outDir));
}));
});
tasks.push(taskName);
var taskName = 'build-translation-fingerprints';
gulp.task(taskName, ['build-merged-translations'], function() {
return gulp.src(outDir + '/*.json')
.pipe(rename({
extname: "",
}))
.pipe(hash({
algorithm: 'md5',
hashLength: 32,
template: '<%= name %>-<%= hash %>.json',
}))
.pipe(hash.manifest('translationFingerprints.json'))
.pipe(transform(function(data, file) {
Object.keys(data).map(function(key, index) {
data[key] = {fingerprint: data[key]};
});
return data;
}))
.pipe(gulp.dest('build-temp'));
});
tasks.push(taskName);
var taskName = 'build-translations';
gulp.task(taskName, ['build-translation-fingerprints', 'build-translation-native-names'], function() {
return gulp.src([
'build-temp/translationFingerprints.json',
'build-temp/translationNativeNames.json',
])
.pipe(merge({}))
.pipe(insert.wrap('<script>\nwindow.translationMetadata = ', ';\n</script>'))
.pipe(rename('translationMetadata.html'))
.pipe(gulp.dest('build-temp'));
});
tasks.push(taskName);
module.exports = tasks;

View File

@ -39,8 +39,14 @@
"gulp-babel": "^7.0.0",
"gulp-file": "^0.3.0",
"gulp-filter": "^5.0.1",
"gulp-foreach": "^0.1.0",
"gulp-hash": "^4.0.1",
"gulp-html-minifier": "^0.1.8",
"gulp-if": "^2.0.2",
"gulp-insert": "^0.5.0",
"gulp-json-transform": "^0.4.2",
"gulp-jsonminify": "^1.0.0",
"gulp-merge-json": "^1.0.0",
"gulp-rename": "^1.2.2",
"gulp-rollup-each": "^2.0.0",
"gulp-uglify": "^3.0.0",

View File

@ -7,6 +7,7 @@
<link rel="import" href="./ha-config-section-core.html">
<!-- <link rel="import" href="./ha-config-section-group.html"> -->
<link rel="import" href="./ha-config-section-hassbian.html">
<link rel="import" href="./ha-config-section-translation.html">
<link rel="import" href="./ha-config-section-themes.html">
<dom-module id="ha-config-core">
@ -53,6 +54,14 @@
hass='[[hass]]'
></ha-config-section-core>
<template is='dom-if' if='[[computeIsTranslationLoaded(hass)]]'>
<div class='border'></div>
<ha-config-section-translation
is-wide='[[isWide]]'
hass='[[hass]]'
></ha-config-section-translation>
</template>
<template is='dom-if' if='[[computeIsThemesLoaded(hass)]]'>
<div class='border'></div>
<ha-config-section-themes
@ -96,6 +105,13 @@ class HaConfigCore extends Polymer.Element {
return window.hassUtil.isComponentLoaded(hass, 'config.zwave');
}
computeIsTranslationLoaded(hass) {
// Return false to hide language selection until i18n is ready to be
// deployed.
return false && hass.translationMetadata &&
Object.keys(hass.translationMetadata).length;
}
computeIsThemesLoaded(hass) {
return hass.themes && hass.themes.themes &&
Object.keys(hass.themes.themes).length;

View File

@ -0,0 +1,79 @@
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../../bower_components/paper-card/paper-card.html">
<link rel="import" href="../../../bower_components/paper-dropdown-menu/paper-dropdown-menu.html">
<link rel='import' href='../../../bower_components/paper-listbox/paper-listbox.html'>
<link rel='import' href='../../../bower_components/paper-item/paper-item.html'>
<link rel='import' href='../../../src/util/hass-mixins.html'>
<link rel="import" href="../ha-config-section.html">
<dom-module id="ha-config-section-translation">
<template>
<ha-config-section is-wide='[[isWide]]'>
<span slot='header'>Choose a Language</span>
<span slot='introduction'>
Choose a language for the Home Assistant interface on this device.
</span>
<paper-card>
<div class='card-content'>
<paper-dropdown-menu label="Language" dynamic-align>
<paper-listbox slot="dropdown-content" on-selected-item-changed="handleLanguageSelect" attr-for-selected="language-tag" selected="[[language]]">
<template is='dom-repeat' items='[[languages]]'>
<paper-item language-tag="[[item.tag]]">[[item.nativeName]]</paper-item>
</template>
</paper-listbox>
></paper-dropdown-menu>
</div>
</paper-card>
</ha-config-section>
</template>
</dom-module>
<script>
/*
* @appliesMixin window.hassMixins.LocalizeMixin
* @appliesMixin window.hassMixins.EventsMixin
*/
class HaConfigSectionTranslation extends
window.hassMixins.LocalizeMixin(window.hassMixins.EventsMixin(Polymer.Element)) {
static get is() { return 'ha-config-section-translation'; }
static get properties() {
return {
hass: {
type: Object,
},
isWide: {
type: Boolean,
},
languages: {
type: Array,
computed: 'computeLanguages(hass)',
},
};
}
computeLanguages(hass) {
if (!hass || !hass.translationMetadata) {
return [];
}
return Object.keys(hass.translationMetadata).map(key => ({
tag: key,
nativeName: hass.translationMetadata[key].nativeName,
}));
}
handleLanguageSelect(ev) {
// Only fire event if language was changed. This prevents select updates when
// responding to hass changes.
if (ev.detail.value && ev.detail.value.languageTag !== this.language) {
this.fire('hass-language-select', { language: ev.detail.value.languageTag });
}
}
}
customElements.define(HaConfigSectionTranslation.is, HaConfigSectionTranslation);
</script>

View File

@ -21,6 +21,10 @@ cp build/*.js build/*.html $OUTPUT_DIR
mkdir $OUTPUT_DIR/panels
cp build/panels/*.html $OUTPUT_DIR/panels
# Panels
mkdir $OUTPUT_DIR/translations
cp build/translations/*.json $OUTPUT_DIR/translations
# Local Roboto
cp -r bower_components/font-roboto-local/fonts $OUTPUT_DIR
@ -43,7 +47,13 @@ cp build/service_worker.js $OUTPUT_DIR
# GZIP frontend
cd $OUTPUT_DIR
gzip -f -n -k -9 *.html *.js ./panels/*.html ./fonts/roboto/*.ttf ./fonts/robotomono/*.ttf
gzip -f -n -k -9 \
*.html \
*.js \
./panels/*.html \
./translations/*.json \
./fonts/roboto/*.ttf \
./fonts/robotomono/*.ttf
cd ..
# Generate the __init__ file

View File

@ -9,6 +9,7 @@
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
<link rel='import' href='../util/hass-mixins.html'>
<link rel='import' href='../util/hass-translation.html'>
<link rel='import' href='./ha-push-notifications-toggle.html'>
@ -105,19 +106,19 @@
<paper-listbox attr-for-selected='data-panel' selected='[[route.panel]]'>
<paper-icon-item on-tap='menuClicked' data-panel='states'>
<iron-icon slot="item-icon" icon='mdi:apps'></iron-icon>
<span class='item-text'>States</span>
<span class='item-text'>{{localize('panel', 'states')}}</span>
</paper-icon-item>
<template is='dom-repeat' items='[[panels]]'>
<paper-icon-item on-tap='menuClicked' data-panel$='[[item.url_path]]'>
<iron-icon slot="item-icon" icon='[[item.icon]]'></iron-icon>
<span class='item-text'>[[item.title]]</span>
<span class='item-text'>{{localize('panel', item.title)}}</span>
</paper-icon-item>
</template>
<paper-icon-item on-tap='menuClicked' data-panel='logout' class='logout'>
<iron-icon slot="item-icon" icon='mdi:exit-to-app'></iron-icon>
<span class='item-text'>Log Out</span>
<span class='item-text'>{{localize('panel', 'log_out')}}</span>
</paper-icon-item>
</paper-listbox>
@ -171,7 +172,12 @@
</dom-module>
<script>
class HaSidebar extends window.hassMixins.EventsMixin(Polymer.Element) {
/*
* @appliesMixin window.hassMixins.LocalizeMixin
* @appliesMixin window.hassMixins.EventsMixin
*/
class HaSidebar extends
window.hassMixins.LocalizeMixin(window.hassMixins.EventsMixin(Polymer.Element)) {
static get is() { return 'ha-sidebar'; }
static get properties() {

View File

@ -1,4 +1,5 @@
<link rel="import" href='../bower_components/polymer/polymer-element.html'>
<link rel='import' href='../bower_components/app-localize-behavior/app-localize-behavior.html'>
<link rel='import' href='./util/roboto.html'>
<link rel='import' href='../bower_components/paper-styles/typography.html'>
<link rel='import' href='../bower_components/iron-flex-layout/iron-flex-layout-classes.html'>
@ -7,6 +8,7 @@
<link rel="import" href="../bower_components/neon-animation/web-animations.html">
<link rel='import' href='./util/hass-translation.html'>
<link rel='import' href='./util/hass-util.html'>
<link rel='import' href='./util/ha-pref-storage.html'>
<link rel='import' href='./util/hass-call-api.html'>
@ -84,7 +86,9 @@ class HomeAssistant extends Polymer.Element {
ready() {
super.ready();
this.addEventListener('settheme', e => this.setTheme(e));
this.addEventListener('hass-language-select', e => this.selectLanguage(e));
this.loadIcons();
this.loadResources();
}
computeShowMain(hass, iconsLoaded) {
@ -112,6 +116,15 @@ class HomeAssistant extends Polymer.Element {
);
}
loadResources() {
window.getTranslation().then((result) => {
this._updateHass({
language: result.language,
resources: result.resources,
});
});
}
connectionChanged(conn, oldConn) {
if (oldConn) {
this.unsubConnection();
@ -128,6 +141,9 @@ class HomeAssistant extends Polymer.Element {
states: null,
config: null,
themes: null,
language: null,
resources: null,
translationMetadata: window.translationMetadata,
dockedSidebar: false,
moreInfoEntityId: null,
callService: (domain, service, serviceData) =>
@ -256,6 +272,12 @@ class HomeAssistant extends Polymer.Element {
this.$.storage.storeState();
}
selectLanguage(event) {
this._updateHass({ selectedLanguage: event.detail.language });
this.$.storage.storeState();
this.loadResources();
}
_updateHass(obj) {
this.hass = Object.assign({}, this.hass, obj);
}

View File

@ -3,6 +3,7 @@
var STORED_STATE = [
'dockedSidebar',
'selectedTheme',
'selectedLanguage',
];
Polymer({

View File

@ -77,4 +77,37 @@ window.hassMixins.NavigateMixin = Polymer.dedupingMixin(superClass =>
}
});
/* @polymerMixin */
window.hassMixins.LocalizeMixin = Polymer.dedupingMixin(superClass =>
class extends Polymer.mixinBehaviors([Polymer.AppLocalizeBehavior], superClass) {
static get properties() {
return {
hass: {
type: Object,
},
language: {
type: String,
computed: 'computeLanguage(hass)',
},
resources: {
type: Object,
computed: 'computeResources(hass)',
},
};
}
computeLanguage(hass) {
return hass && hass.language;
}
computeResources(hass) {
return hass && hass.resources;
}
localize(namespace, message, ...args) {
// Return the input message if no translation is found
return super.localize(namespace + '.' + message, ...args) || message;
}
});
</script>

View File

@ -0,0 +1,84 @@
<link rel='import' href='../../build-temp/translationMetadata.html' />
<script>
function getActiveTranslation() {
// Perform case-insenstive comparison since browser isn't required to
// report languages with specific cases.
const lookup = {};
/* eslint-disable no-undef */
Object.keys(window.translationMetadata).forEach(function (tr) {
lookup[tr.toLowerCase()] = tr;
});
// Search for a matching translation from most specific to general
function languageGetTranslation(language) {
const subtags = language.toLowerCase().split('-');
for (var i = subtags.length; i >= 1; i--) {
const lang = subtags.slice(0, i).join('-');
if (lookup[lang]) {
return lookup[lang];
}
}
return null;
}
var translation = null;
if (window.localStorage.selectedLanguage) {
translation = languageGetTranslation(JSON.parse(window.localStorage.selectedLanguage));
if (translation) {
return translation;
}
} else if (navigator.languages) {
for (var i = 0; i < navigator.languages.length; i++) {
translation = languageGetTranslation(navigator.languages[i]);
if (translation) {
return translation;
}
}
} else {
translation = languageGetTranslation(navigator.language || navigator.userLanguage);
if (translation) {
return translation;
}
}
// Final fallback
return 'en';
}
// Store loaded translations in memory so translations are available immediately
// when DOM is created in Polymer. Even a cache lookup creates noticable latency.
const translations = {};
window.getTranslation = function (translationInput) {
const translation = translationInput || getActiveTranslation();
const translationFingerprint = window.translationMetadata[translation].fingerprint;
// Create a promise to fetch translation from the server
if (!translations[translationFingerprint]) {
translations[translationFingerprint] =
fetch('/static/translations/' + translationFingerprint)
.then(response => response.json()).then(data => ({
language: translation,
resources: {
[translation]: data
},
}))
.catch((error) => {
delete translations[translationFingerprint];
if (translationInput !== 'en') {
// Couldn't load selected translation. Try a fall back to en before failing.
return window.getTranslation('en');
}
return Promise.reject(error);
});
}
return translations[translationFingerprint];
};
// Load selected translation into memory immediately so it is ready when Polymer
// initializes.
window.getTranslation();
</script>

12
translations/en.json Normal file
View File

@ -0,0 +1,12 @@
{
"language": {
"en": "English"
},
"panel": {
"states": "States",
"map": "Map",
"logbook": "Logbook",
"history": "History",
"log_out": "Log Out"
}
}

164
yarn.lock
View File

@ -2378,6 +2378,10 @@ depd@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa"
deprecate@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/deprecate/-/deprecate-1.0.0.tgz#661490ed2428916a6c8883d8834e5646f4e4a4a8"
deprecated@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19"
@ -2673,7 +2677,7 @@ es6-object-assign@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
es6-promise@^2.1.0:
es6-promise@^2.1.0, es6-promise@^2.1.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-2.3.0.tgz#96edb9f2fdb01995822b263dd8aadab6748181bc"
@ -3646,6 +3650,23 @@ gulp-filter@^5.0.1:
multimatch "^2.0.0"
streamfilter "^1.0.5"
gulp-foreach@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/gulp-foreach/-/gulp-foreach-0.1.0.tgz#2547abfd1a1b75f569b54194190ef1c0b0289538"
dependencies:
gulp-util "~2.2.14"
through2 "~0.6.3"
gulp-hash@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/gulp-hash/-/gulp-hash-4.1.1.tgz#7779910f6c378686b62f88abbb57ebeef20945d0"
dependencies:
es6-promise "^2.1.1"
gulp-util "^2.2.17"
lodash.assign "^2.4.1"
lodash.template "^2.4.1"
through2 "^2.0.0"
gulp-html-minifier@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/gulp-html-minifier/-/gulp-html-minifier-0.1.8.tgz#b0dd99d96ad01c0a88d79465ed44f94d6a2b549a"
@ -3663,12 +3684,45 @@ gulp-if@^2.0.0, gulp-if@^2.0.2:
ternary-stream "^2.0.1"
through2 "^2.0.1"
gulp-insert@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/gulp-insert/-/gulp-insert-0.5.0.tgz#32313f13e4a23cf5acca5ce5f0c080923c778602"
dependencies:
readable-stream "^1.0.26-4"
streamqueue "0.0.6"
gulp-json-transform@^0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/gulp-json-transform/-/gulp-json-transform-0.4.2.tgz#2245ed252fa309c97b8bdd9d1a19cdd149e32685"
dependencies:
gulp-util "^3.0.7"
promise "^7.1.1"
through2 "^2.0.1"
gulp-jsonminify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gulp-jsonminify/-/gulp-jsonminify-1.0.0.tgz#c42b68812c657e6e0a12e93c2aa217b3eb38c70a"
dependencies:
gulp-util "~3.0.4"
jsonminify "~0.2.3"
through2 "~0.6.5"
gulp-match@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/gulp-match/-/gulp-match-1.0.3.tgz#91c7c0d7f29becd6606d57d80a7f8776a87aba8e"
dependencies:
minimatch "^3.0.3"
gulp-merge-json@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gulp-merge-json/-/gulp-merge-json-1.2.0.tgz#8b68f297505278a8143a6fa5bb9830192e4e3ea1"
dependencies:
deprecate "^1.0.0"
gulp-util "^3.0.8"
json5 "^0.5.1"
lodash "^4.17.4"
through "^2.3.8"
gulp-rename@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.2.2.tgz#3ad4428763f05e2764dec1c67d868db275687817"
@ -3703,7 +3757,7 @@ gulp-uglify@^3.0.0:
uglify-js "^3.0.5"
vinyl-sourcemaps-apply "^0.2.0"
gulp-util@^2.2.14, gulp-util@^2.2.20:
gulp-util@^2.2.14, gulp-util@^2.2.17, gulp-util@^2.2.20, gulp-util@~2.2.14:
version "2.2.20"
resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-2.2.20.tgz#d7146e5728910bd8f047a6b0b1e549bc22dbd64c"
dependencies:
@ -3716,7 +3770,7 @@ gulp-util@^2.2.14, gulp-util@^2.2.20:
through2 "^0.5.0"
vinyl "^0.2.1"
gulp-util@^3.0.0, gulp-util@^3.0.6, gulp-util@^3.0.8:
gulp-util@^3.0.0, gulp-util@^3.0.6, gulp-util@^3.0.7, gulp-util@^3.0.8, gulp-util@~3.0.4:
version "3.0.8"
resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f"
dependencies:
@ -4488,6 +4542,10 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
jsonminify@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/jsonminify/-/jsonminify-0.2.3.tgz#4b842c8a3fe5d6aa48b2f8f95a1cf9a80c019d8e"
jsonpointer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
@ -4608,6 +4666,15 @@ lodash._baseassign@^3.0.0:
lodash._basecopy "^3.0.0"
lodash.keys "^3.0.0"
lodash._basebind@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._basebind/-/lodash._basebind-2.4.1.tgz#e940b9ebdd27c327e0a8dab1b55916c5341e9575"
dependencies:
lodash._basecreate "~2.4.1"
lodash._setbinddata "~2.4.1"
lodash._slice "~2.4.1"
lodash.isobject "~2.4.1"
lodash._basecopy@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
@ -4616,6 +4683,32 @@ lodash._basecreate@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
lodash._basecreate@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-2.4.1.tgz#f8e6f5b578a9e34e541179b56b8eeebf4a287e08"
dependencies:
lodash._isnative "~2.4.1"
lodash.isobject "~2.4.1"
lodash.noop "~2.4.1"
lodash._basecreatecallback@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._basecreatecallback/-/lodash._basecreatecallback-2.4.1.tgz#7d0b267649cb29e7a139d0103b7c11fae84e4851"
dependencies:
lodash._setbinddata "~2.4.1"
lodash.bind "~2.4.1"
lodash.identity "~2.4.1"
lodash.support "~2.4.1"
lodash._basecreatewrapper@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.4.1.tgz#4d31f2e7de7e134fbf2803762b8150b32519666f"
dependencies:
lodash._basecreate "~2.4.1"
lodash._setbinddata "~2.4.1"
lodash._slice "~2.4.1"
lodash.isobject "~2.4.1"
lodash._basetostring@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5"
@ -4624,6 +4717,15 @@ lodash._basevalues@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7"
lodash._createwrapper@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._createwrapper/-/lodash._createwrapper-2.4.1.tgz#51d6957973da4ed556e37290d8c1a18c53de1607"
dependencies:
lodash._basebind "~2.4.1"
lodash._basecreatewrapper "~2.4.1"
lodash._slice "~2.4.1"
lodash.isfunction "~2.4.1"
lodash._escapehtmlchar@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz#df67c3bb6b7e8e1e831ab48bfa0795b92afe899d"
@ -4681,12 +4783,38 @@ lodash._root@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
lodash._setbinddata@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._setbinddata/-/lodash._setbinddata-2.4.1.tgz#f7c200cd1b92ef236b399eecf73c648d17aa94d2"
dependencies:
lodash._isnative "~2.4.1"
lodash.noop "~2.4.1"
lodash._shimkeys@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz#6e9cc9666ff081f0b5a6c978b83e242e6949d203"
dependencies:
lodash._objecttypes "~2.4.1"
lodash._slice@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash._slice/-/lodash._slice-2.4.1.tgz#745cf41a53597b18f688898544405efa2b06d90f"
lodash.assign@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-2.4.1.tgz#84c39596dd71181a97b0652913a7c9675e49b1aa"
dependencies:
lodash._basecreatecallback "~2.4.1"
lodash._objecttypes "~2.4.1"
lodash.keys "~2.4.1"
lodash.bind@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-2.4.1.tgz#5d19fa005c8c4d236faf4742c7b7a1fcabe29267"
dependencies:
lodash._createwrapper "~2.4.1"
lodash._slice "~2.4.1"
lodash.cond@^4.3.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
@ -4724,6 +4852,10 @@ lodash.escape@~2.4.1:
lodash._reunescapedhtml "~2.4.1"
lodash.keys "~2.4.1"
lodash.identity@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.identity/-/lodash.identity-2.4.1.tgz#6694cffa65fef931f7c31ce86c74597cf560f4f1"
lodash.isarguments@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
@ -4736,6 +4868,10 @@ lodash.isequal@^4.0.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
lodash.isfunction@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz#2cfd575c73e498ab57e319b77fa02adef13a94d1"
lodash.isobject@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5"
@ -4770,6 +4906,10 @@ lodash.mapvalues@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
lodash.noop@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-2.4.1.tgz#4fb54f816652e5ae10e8f72f717a388c7326538a"
lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
@ -4778,6 +4918,12 @@ lodash.some@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
lodash.support@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.support/-/lodash.support-2.4.1.tgz#320e0b67031673c28d7a2bb5d9e0331a45240515"
dependencies:
lodash._isnative "~2.4.1"
lodash.template@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-2.4.1.tgz#9e611007edf629129a974ab3c48b817b3e1cf20d"
@ -6139,7 +6285,7 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
readable-stream@1.1.x, readable-stream@~1.1.9:
readable-stream@1.1.x, readable-stream@^1.0.26-2, readable-stream@^1.0.26-4, readable-stream@~1.1.9:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
dependencies:
@ -6979,6 +7125,12 @@ streamfilter@^1.0.5:
dependencies:
readable-stream "^2.0.2"
streamqueue@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/streamqueue/-/streamqueue-0.0.6.tgz#66f5f5ec94e9b8af249e4aec2dd1f741bfe94de3"
dependencies:
readable-stream "^1.0.26-2"
streamsearch@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
@ -7266,7 +7418,7 @@ through2@^0.5.0:
readable-stream "~1.0.17"
xtend "~3.0.0"
through2@^0.6.0, through2@^0.6.1:
through2@^0.6.0, through2@^0.6.1, through2@~0.6.3, through2@~0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
dependencies:
@ -7280,7 +7432,7 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0:
readable-stream "^2.1.5"
xtend "~4.0.1"
through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"