diff --git a/.eslintrc b/.eslintrc index cfbf631..9a44ac7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,7 +3,9 @@ "rules": { "func-names": 0, "no-tabs": 0, - "react/jsx-filename-extension": 0, - "indent": ["error", "tab"] + "max-len": 1, + "class-methods-use-this": 0, + "indent": [2, "tab"], + "react/jsx-filename-extension": 0 } } \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cd30b24 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "6" +script: + - "npm test" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f9de2f4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Change Log + +## v0.3.0 (2017-06-26) + +**Implemented enhancements:** + +- Added nxgettext function which allows to translate plural strings with context. +- Extracted HOC into a separate component and updated it to inherit from that standalone component. \ No newline at end of file diff --git a/README.md b/README.md index c8e71f2..3a80524 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,21 @@ -# react-gettext +# react-gettext 0.3.0 + +[![Build Status](https://travis-ci.org/eugene-manuilov/react-gettext.svg?branch=master)](https://travis-ci.org/eugene-manuilov/react-gettext) Tiny React library for implementing gettext localization in your application. It provides HOC function to enhance your application by exposing gettext functions in the context scope. ## Instalation -React Gettext requires **React 15.0 or later**. +React Gettext requires **React 15.0 or later**. You can add this package using following commands: ``` npm install react-gettext --save ``` +``` +yarn add react-gettext +``` + ## Usage Let's assume you have following React application: @@ -55,7 +61,7 @@ To make it translatable you need to update your `app.js` file to use HOC functio ```diff // app.js import React, { Component } from 'react'; -+ import Textdomain from 'react-gettext'; ++ import withGettext from 'react-gettext'; import Header from './Header'; import Footer from './Footer'; @@ -64,15 +70,16 @@ To make it translatable you need to update your `app.js` file to use HOC functio ... } -+ export default Textdomain({...}, 'n != 1')(App); ++ export default withGettext({...}, 'n != 1')(App); ``` -After doing it you can start using `gettext`, `ngettext` and `xgettext` functions in your descending components: +After doing it you can start using `gettext`, `ngettext`, `xgettext` and `nxgettext` functions in your descending components: ```diff // Header.js - import React, { Component } from 'react'; -+ import React, { Component, PropTypes } from 'react'; ++ import React, { Component } from 'react'; ++ import PropTypes from 'prop-types'; export default class Header extends Component { @@ -88,13 +95,14 @@ After doing it you can start using `gettext`, `ngettext` and `xgettext` function + Header.contextTypes = { + gettext: PropTypes.func.isRequired, + ngettext: PropTypes.func.isRequired, -+ xgettext: PropTypes.func.isRequired ++ xgettext: PropTypes.func.isRequired, ++ nxgettext: PropTypes.func.isRequired, + }; ``` ## Documentation -### Textdomain(translations, pluralForms) +### withGettext(translations, pluralForms) Higher-order function which is exported by default from `react-gettext` package. It accepts two arguments and returns function to create higher-order component. @@ -111,7 +119,7 @@ const translations = { const pluralForms = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'; // 3 plural forms for Russian, Belarusian, Bosnian, Croatian, Serbian, Ukrainian, etc. -const HOC = Textdomain(translations, pluralForms)(App); +const HOC = withGettext(translations, pluralForms)(App); ``` ```javascript @@ -126,9 +134,31 @@ function getPluralForms(n) { return n > 1 ? 1 : 0; } -const HOC = Textdomain(getTranslations, getPluralForms)(App); +const HOC = withGettext(getTranslations, getPluralForms)(App); ``` +As an alternative you can pass translations and plural form as properties to higher-order-component, like this: + +```javascript +function getTranslations() { + return { + 'Some text': 'Some translated text', + ... + }; +} + +function getPluralForms(n) { + return n > 1 ? 1 : 0; +} + +const HOC = withGettext()(App); + +... + +ReactDOM.render(..., ...); +``` + + ### gettext(message) The function to translate a string. Accepts original message and returns translation if it exists, otherwise original message. @@ -171,14 +201,49 @@ Example: this.context.xgettext('some text', 'context where this message is used'); ``` +### nxgettext(singular, plural, n, context) + +The function to translate plural string based on a specific context. Accepts singular and plural messages along with a number to calculate plural form against and context string. Returns translated message based on plural form if it exists, otherwise original message based on **n** value. + +- **singular**: a string to be translated when count is not plural +- **plural**: a string to be translated when count is plural +- **n**: a number to count plural form +- **context**: A context to search translation in. + +Example: + +```javascript +// somewhere in your jsx component +this.context.nxgettext('day ago', 'days ago', numberOfDays, 'Article publish date'); +``` + ## Poedit -If you use Poedit app to translate your messages, then you can use `gettext;ngettext:1,2;xgettext:1,2c` as keywords list to properly parse and extract strings from your javascript files. +If you use Poedit app to translate your messages, then you can use `gettext;ngettext:1,2;xgettext:1,2c;nxgettext:1,2,4c` as keywords list to properly parse and extract strings from your javascript files. + +Here is an example of a **POT** file which you can start with: + +``` +msgid "" +msgstr "" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Basepath: ./src\n" +"X-Poedit-KeywordsList: gettext;ngettext:1,2;xgettext:1,2c;nxgettext:1,2,4c\n" +"X-Poedit-SourceCharset: UTF-8\n" +``` ## Contribute -What to help or have a suggestion? Open a [new ticket](https://github.com/eugene-manuilov/react-gettext/issues/new) and we can discuss it or submit pull request. Please, make sure you run `npm run test` before submitting a pull request. +What to help or have a suggestion? Open a [new ticket](https://github.com/eugene-manuilov/react-gettext/issues/new) and we can discuss it or submit pull request. Please, make sure you run `npm test` before submitting a pull request. ## License -MIT \ No newline at end of file +MIT diff --git a/__tests__/__snapshots__/context.js.snap b/__tests__/__snapshots__/context.js.snap deleted file mode 100644 index 2383584..0000000 --- a/__tests__/__snapshots__/context.js.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Child component can use context functions 1`] = ` - -`; diff --git a/__tests__/context.js b/__tests__/context.js index a0ff835..a34deba 100644 --- a/__tests__/context.js +++ b/__tests__/context.js @@ -1,68 +1,149 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { mount } from 'enzyme'; import faker from 'faker'; -import renderer from 'react-test-renderer'; - -import Textdomain from '../lib/index'; - -class App extends Component { - render() { - return ; - } -}; - -class C1 extends Component { - render() { - return ( - - ); - } -}; - -C1.contextTypes = { - gettext: PropTypes.func.isRequired, - xgettext: PropTypes.func.isRequired, - ngettext: PropTypes.func.isRequired -}; - -test('Child component can use context functions', () => { - const po = {}; - const sentence1 = faker.lorem.sentence(); - const sentence2 = faker.lorem.sentence(); - const sentence3 = faker.lorem.sentence(); - const sentence4 = faker.lorem.sentence(); - const sentence5 = faker.lorem.sentence(); - - po[sentence1] = '7[|UyK-%T`CKw-%j $1/6XTzdd(8+cY,/JCngNVv+wQO6NAv:+^=4GA[,+G@^GI<'; - po[`${sentence3}\u0004${sentence2}`] = 'T6%(h7m%J*O(Bm6!FiRk0 9;V]r`kPz-ROW7E8*t.Aki9}j=hU9xfU8X|.7wEuZ0'; - po[sentence4] = [ - 'ZgcaUXD7A~@-`y2%xm@t{TLsN1LvUL`6zamv|e}G|rY`ms-|aMxn#_2f{', - 'v.,rfN>-w1>j#KBX%eRO[nm@|MgGv,8E.o8-|`t.9Sz{9n#k=+V?W.#k@/tjWq|>' + +import withGettext from '../lib/index'; + +describe('Higher-order-component translates', () => { + const pluralForm = 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'; + const catalog = {}; + + const gettext1 = faker.lorem.sentence(); + const gettext2 = faker.lorem.sentence(); + catalog[gettext1] = faker.lorem.sentence(); + + const single1 = faker.lorem.sentence(); + const plural1 = faker.lorem.sentence(); + catalog[single1] = [ + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), ]; - const hoc = Textdomain(po, 'n!=1')(App); - const component = renderer.create(React.createElement(hoc, { - gettext: sentence1, - xgettext: sentence2, - context: sentence3, - singular: sentence4, - plural: sentence5, - num: 2 - }, [])); - - let tree = component.toJSON(); - expect(tree).toMatchSnapshot(); -}); \ No newline at end of file + const single2 = faker.lorem.sentence(); + const plural2 = faker.lorem.sentence(); + + const context1 = faker.lorem.sentence(); + const gettext3 = faker.lorem.sentence(); + const contextKey1 = `${context1}\u0004${gettext3}`; + catalog[contextKey1] = faker.lorem.sentence(); + + const context2 = faker.lorem.sentence(); + const gettext4 = faker.lorem.sentence(); + + const single3 = faker.lorem.sentence(); + const plural3 = faker.lorem.sentence(); + const context3 = faker.lorem.sentence(); + const contextKey2 = `${context3}\u0004${single3}`; + catalog[contextKey2] = [ + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), + ]; + + const single4 = faker.lorem.sentence(); + const plural4 = faker.lorem.sentence(); + const context4 = faker.lorem.sentence(); + + class baseComponent extends Component { + render() { + const { type, num } = this.props; + + let message = ''; + switch (type) { + case 'gettext1': + message = this.context.gettext(gettext1); + break; + case 'gettext2': + message = this.context.gettext(gettext2); + break; + case 'ngettext1': + message = this.context.ngettext(single1, plural1, num); + break; + case 'ngettext2': + message = this.context.ngettext(single2, plural2, num); + break; + case 'xgettext1': + message = this.context.xgettext(gettext3, context1); + break; + case 'xgettext2': + message = this.context.xgettext(gettext4, context2); + break; + case 'nxgettext1': + message = this.context.nxgettext(single3, plural3, num, context3); + break; + case 'nxgettext2': + message = this.context.nxgettext(single4, plural4, num, context4); + break; + } + + return
{message}
; + } + }; + + baseComponent.contextTypes = { + gettext: PropTypes.func.isRequired, + xgettext: PropTypes.func.isRequired, + ngettext: PropTypes.func.isRequired, + nxgettext: PropTypes.func.isRequired, + }; + + const Textdomain = withGettext(catalog, pluralForm)(baseComponent); + + test('gettext', () => { + let wrapper; + + // check when translation exists + wrapper = mount(); + expect(wrapper.text()).toBe(catalog[gettext1]); + + // check when translation exists + wrapper = mount(); + expect(wrapper.text()).toBe(gettext2); + }); + + test('ngettext', () => { + let wrapper; + + // check when translation exists + for (let i = 0; i < 3; i++) { + wrapper = mount(); + expect(wrapper.text()).toBe(catalog[single1][i]); + } + + // check fallbacks when a translation doesn't exist + for (let i = 0; i < 2; i++) { + wrapper = mount(); + expect(wrapper.text()).toBe(i == 0 ? single2 : plural2); + } + }); + + test('xgettext', () => { + let wrapper; + + // check when translation exists + wrapper = mount(); + expect(wrapper.text()).toBe(catalog[contextKey1]); + + // check when translation exists + wrapper = mount(); + expect(wrapper.text()).toBe(gettext4); + }); + + test('ngettext', () => { + let wrapper; + + // check when translation exists + for (let i = 0; i < 3; i++) { + wrapper = mount(); + expect(wrapper.text()).toBe(catalog[contextKey2][i]); + } + + // check fallbacks when a translation doesn't exist + for (let i = 0; i < 2; i++) { + wrapper = mount(); + expect(wrapper.text()).toBe(i == 0 ? single4 : plural4); + } + }); +}); diff --git a/__tests__/default-export.js b/__tests__/default-export.js deleted file mode 100644 index 6e241d9..0000000 --- a/__tests__/default-export.js +++ /dev/null @@ -1,9 +0,0 @@ -import Textdomain from '../lib/index'; - -test('Test default export', () => { - expect(typeof Textdomain).toBe('function'); -}); - -test('Test HOF returns HOC function', () => { - expect(typeof Textdomain({}, '')).toBe('function'); -}); \ No newline at end of file diff --git a/__tests__/higher-order-component.js b/__tests__/higher-order-component.js deleted file mode 100644 index 46b3025..0000000 --- a/__tests__/higher-order-component.js +++ /dev/null @@ -1,56 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import faker from 'faker'; - -import Textdomain from '../lib/index'; - -const getComponent = () => class extends Component { - render() { - return React.Children.only(this.props.children); - } -}; - -describe('Higher-order component', () => { - const name = faker.lorem.word(); - - const baseComponent = getComponent(); - baseComponent.displayName = name; - - const hoc = Textdomain({}, '')(baseComponent); - - test('is React Component', () => { - expect(Component.isPrototypeOf(hoc)).toBeTruthy(); - }); - - test('has proper display name', () => { - expect(hoc.displayName).toBe(`WithGettext(${name})`); - }); - - test('has childContextTypes', () => { - expect(hoc.childContextTypes).toEqual({ - gettext: PropTypes.func, - ngettext: PropTypes.func, - xgettext: PropTypes.func - }); - }); - - test('has static functions for context types', () => { - expect(typeof hoc.gettext).toBe('function'); - expect(typeof hoc.ngettext).toBe('function'); - expect(typeof hoc.xgettext).toBe('function'); - }); -}); - -describe('Higher-order component instance', () => { - const baseComponent = getComponent(); - const hoc = Textdomain({}, '')(baseComponent); - - test('returns child context', () => { - const hocObject = new hoc({}); - expect(hocObject.getChildContext()).toEqual({ - gettext: hoc.gettext, - ngettext: hoc.ngettext, - xgettext: hoc.xgettext - }); - }); -}); \ No newline at end of file diff --git a/__tests__/hoc.js b/__tests__/hoc.js new file mode 100644 index 0000000..807eeb6 --- /dev/null +++ b/__tests__/hoc.js @@ -0,0 +1,43 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; +import faker from 'faker'; + +import withGettext from '../lib/index'; + +describe('Higher-order-component', () => { + class baseComponent extends Component { + render() { + return this.props.children; + } + }; + + const name = faker.lorem.word(); + baseComponent.displayName = name; + + const plural = 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2'; + const Textdomain = withGettext({}, plural)(baseComponent); + + test('is React component', () => { + expect(Component.isPrototypeOf(Textdomain)).toBeTruthy(); + }); + + test('has proper displayName', () => { + expect(Textdomain.displayName).toBe(`withGettext(${name})`); + }); + + test('has proper childContextType', () => { + expect(Textdomain.childContextTypes).toEqual({ + gettext: PropTypes.func, + ngettext: PropTypes.func, + xgettext: PropTypes.func, + nxgettext: PropTypes.func, + }); + }); + + test('properly calculates plural form', () => { + const instance = new Textdomain({plural}); + expect(instance.getPluralForm(1)).toBe(0); + expect(instance.getPluralForm(2)).toBe(1); + expect(instance.getPluralForm(5)).toBe(2); + }); +}); diff --git a/__tests__/translations.js b/__tests__/translations.js deleted file mode 100644 index 97e0cf4..0000000 --- a/__tests__/translations.js +++ /dev/null @@ -1,107 +0,0 @@ -import React, { Component } from 'react'; -import faker from 'faker'; - -import Textdomain from '../lib/index'; - -const getComponent = () => class extends Component { - render() { - return null; - } -}; - -test('Default values when translations not found', () => { - const baseComponent = getComponent(); - const hoc = Textdomain({}, '')(baseComponent); - - let sentence = ''; - - sentence = faker.lorem.sentence(); - expect(hoc.gettext(sentence)).toBe(sentence); - - sentence = faker.lorem.sentence(); - expect(hoc.xgettext(sentence, 'context')).toBe(sentence); - - const sentence1 = faker.lorem.sentence(); - const sentence2 = faker.lorem.sentence(); - const number = faker.random.number({min: 1, max: 3}); - const expected = number > 1 ? sentence2 : sentence1; - expect(hoc.ngettext(sentence1, sentence2, number)).toBe(expected); -}); - -test('Test gettext function', () => { - const sentence = faker.lorem.sentence(); - const translation = faker.lorem.sentence(); - - const po = {}; - po[sentence] = translation; - - const baseComponent = getComponent(); - const hoc = Textdomain(po, '')(baseComponent); - - expect(hoc.gettext(sentence)).toBe(translation); -}); - -test('Test xgettext function', () => { - const sentence = faker.lorem.sentence(); - const context = faker.lorem.sentence(); - const translation = faker.lorem.sentence(); - - const po = {}; - po[`${context}\u0004${sentence}`] = translation; - - const baseComponent = getComponent(); - const hoc = Textdomain(po, '')(baseComponent); - - expect(hoc.xgettext(sentence, context)).toBe(translation); - expect(hoc.xgettext(sentence, faker.lorem.sentence())).not.toBe(translation); -}); - -test('Test ngettext function', () => { - const sentence = faker.lorem.sentence(); - const translation1 = faker.lorem.sentence(); - const translation2 = faker.lorem.sentence(); - const translation3 = faker.lorem.sentence(); - - const po = {}; - po[sentence] = [translation1, translation2, translation3]; - - const baseComponent = getComponent(); - const hoc = Textdomain(po, 'n % 3 == 2 ? 2 : n % 2 == 1 ? 1 : 0')(baseComponent); - - const n = faker.random.number(); - const translation = po[sentence][n % 3 == 2 ? 2 : n % 2 == 1 ? 1 : 0]; - expect(hoc.ngettext(sentence, faker.lorem.sentence(), n)).toBe(translation); -}); - -test('Function callbacks for translations and plural forms', () => { - const sentence1 = faker.lorem.sentence(); - const sentence2 = faker.lorem.sentence(); - const context = faker.lorem.sentence(); - - const sentence3 = faker.lorem.sentence(); - const translation1 = faker.lorem.sentence(); - const translation2 = faker.lorem.sentence(); - const translation3 = faker.lorem.sentence(); - - const getPO = () => { - const po = {}; - - po[sentence1] = translation1; - po[`${context}\u0004${sentence2}`] = translation2; - po[sentence3] = [translation1, translation2, translation3]; - - return po; - }; - - const pluralForm = n => n % 3 == 2 ? 2 : n % 2 == 1 ? 1 : 0; - - const baseComponent = getComponent(); - const hoc = Textdomain(getPO, pluralForm)(baseComponent); - - expect(hoc.gettext(sentence1)).toBe(translation1); - expect(hoc.xgettext(sentence2, context)).toBe(translation2); - - const n = faker.random.number(); - const translation = getPO()[sentence3][n % 3 == 2 ? 2 : n % 2 == 1 ? 1 : 0]; - expect(hoc.ngettext(sentence3, faker.lorem.sentence(), n)).toBe(translation); -}) \ No newline at end of file diff --git a/package.json b/package.json index e0e0c20..44abffc 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "bugs": { "url": "https://github.com/eugene-manuilov/react-gettext/issues" }, - "version": "0.2.0", + "version": "0.3.0", "main": "lib/index", "files": [ "*.md", @@ -42,6 +42,7 @@ "babel-preset-env": "^1.4.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", + "enzyme": "^2.9.1", "eslint": "^3.19.0", "eslint-config-airbnb": "^14.1.0", "eslint-loader": "^1.7.1", @@ -49,7 +50,9 @@ "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", "faker": "^4.1.0", + "hoist-non-react-statics": "^1.2.0", "jest": "^19.0.2", + "jest-enzyme": "^3.3.0", "prop-types": "^15.5.8", "react": "^15.5.4", "react-dom": "^15.5.4", @@ -57,10 +60,8 @@ "webpack": "^2.4.1" }, "peerDependencies": { + "hoist-non-react-statics": "^1.2.0", "prop-types": "^15.0.0-0 || ^16.0.0-0", "react": "^15.0.0-0 || ^16.0.0-0" - }, - "dependencies": { - "hoist-non-react-statics": "^1.2.0" } } diff --git a/src/Textdomain.js b/src/Textdomain.js new file mode 100644 index 0000000..c6c51c3 --- /dev/null +++ b/src/Textdomain.js @@ -0,0 +1,133 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; + +class Textdomain extends Component { + + getChildContext() { + const self = this; + + return { + gettext: self.gettext.bind(self), + xgettext: self.xgettext.bind(self), + ngettext: self.ngettext.bind(self), + nxgettext: self.nxgettext.bind(self), + }; + } + + getTranslations() { + const { translations } = this.props; + return typeof translations === 'function' ? translations() : translations; + } + + getPluralForm(n) { + const { plural } = this.props; + + // return 0 if n is not integer + if (isNaN(parseInt(n, 10))) { + return 0; + } + + // if pluralForm is function, use it to get plural form index + if (typeof plural === 'function') { + return plural(n); + } + + // if pluralForm is string and contains only "n", "0-9", " ", "!=?:%+-/*><&|()" + // characters, then we can "eval" it to calculate plural form + if (typeof plural === 'string' && !plural.match(/[^n0-9 !=?:%+-/*><&|()]/i)) { + /* eslint-disable no-new-func */ + const calcPlural = Function('n', `return ${plural}`); + /* eslint-enable no-new-func */ + return +calcPlural(n); + } + + return 0; + } + + getDelimiter() { + return '\u0004'; // End of Transmission (EOT) + } + + gettext(message) { + const messages = this.getTranslations(); + return Object.prototype.hasOwnProperty.call(messages, message) + ? messages[message] + : message; + } + + ngettext(singular, plural, n) { + const self = this; + const messages = self.getTranslations(); + const pluralIndex = self.getPluralForm(n); + const defaultValue = n > 1 ? plural : singular; + + return Object.prototype.hasOwnProperty.call(messages, singular) + && Array.isArray(messages[singular]) + && messages[singular].length > pluralIndex + && pluralIndex >= 0 + ? messages[singular][pluralIndex] + : defaultValue; + } + + xgettext(message, context) { + const self = this; + const EOT = self.getDelimiter(); + const messages = self.getTranslations(); + const key = context + EOT + message; + + return Object.prototype.hasOwnProperty.call(messages, key) + ? messages[key] + : message; + } + + nxgettext(singular, plural, n, context) { + const self = this; + const messages = self.getTranslations(); + const pluralIndex = self.getPluralForm(n); + const defaultValue = n > 1 ? plural : singular; + const EOT = self.getDelimiter(); + const key = context + EOT + singular; + + return Object.prototype.hasOwnProperty.call(messages, key) + && Array.isArray(messages[key]) + && messages[key].length > pluralIndex + && pluralIndex >= 0 + ? messages[key][pluralIndex] + : defaultValue; + } + + render() { + return this.props.children; + } + +} + +Textdomain.propTypes = { + translations: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.objectOf(PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.string), + ])), + ]), + plural: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + ]), + children: PropTypes.arrayOf(PropTypes.node), +}; + +Textdomain.defaultProps = { + translations: {}, + plural: 'n != 1', + children: [], +}; + +Textdomain.childContextTypes = { + gettext: PropTypes.func, + ngettext: PropTypes.func, + xgettext: PropTypes.func, + nxgettext: PropTypes.func, +}; + +export default Textdomain; diff --git a/src/index.js b/src/index.js index 25ec5db..22eff52 100644 --- a/src/index.js +++ b/src/index.js @@ -1,85 +1,24 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import hoistNonReactStatic from 'hoist-non-react-statics'; +import Textdomain from './Textdomain'; -export default function textdomain(translations, pluralForm) { - const EOT = '\u0004'; // End of Transmission - - const getTranslations = function () { - // if translations is function, call it to get translations object, - // otherwise just use incoming translations object - return typeof translations === 'function' ? translations() : translations; - }; - - const getPluralForm = (n) => { - // return 0 if n is not integer - if (isNaN(parseInt(n, 10))) { - return 0; - } - - // if pluralForm is function, use it to get plural form index - if (typeof pluralForm === 'function') { - return pluralForm(n); - } - - // if pluralForm is string and contains only "n", "0-9", " ", "=?:%+-/*><&|" - // characters, then we can eval it to calculate plural form - if (typeof pluralForm === 'string' && !pluralForm.match(/[^n0-9 =?:%+-/*><&|]/i)) { - /* eslint-disable no-eval */ - return +eval(pluralForm.toLowerCase().split('n').join(n)); - /* eslint-enable no-eval */ - } - - return 0; - }; - - // return HOC function +export default function withGettext(translations = {}, pluralForm = 'n != 1') { return (WrappedComponent) => { - class WithGettext extends Component { - static gettext(message) { - const messages = getTranslations(); - - return messages[message] ? messages[message] : message; - } - - static ngettext(singular, plural, n) { - const messages = getTranslations(); - const pluralIndex = getPluralForm(n); - const defaultValue = n > 1 ? plural : singular; - - return Array.isArray(messages[singular]) && messages[singular][pluralIndex] - ? messages[singular][pluralIndex] - : defaultValue; - } - - static xgettext(message, context) { - const messages = getTranslations(); - const key = context + EOT + message; - - return messages[key] ? messages[key] : message; - } - - getChildContext() { - return { - gettext: WithGettext.gettext, - ngettext: WithGettext.ngettext, - xgettext: WithGettext.xgettext, - }; - } + class WithGettext extends Textdomain { render() { return React.createElement(WrappedComponent, this.props); } - } - WithGettext.displayName = `WithGettext(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; + } - WithGettext.childContextTypes = { - gettext: PropTypes.func, - ngettext: PropTypes.func, - xgettext: PropTypes.func, + WithGettext.defaultProps = { + translations, + plural: pluralForm, }; + WithGettext.displayName = `withGettext(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; + return hoistNonReactStatic(WithGettext, WrappedComponent); }; } diff --git a/yarn.lock b/yarn.lock index e5b9411..2208739 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,10 @@ # yarn lockfile v1 +"@types/react@^15.0.22": + version "15.0.31" + resolved "https://registry.yarnpkg.com/@types/react/-/react-15.0.31.tgz#21dfc5d41ee1600ff7d7b738ad21a9502aa3ecf2" + abab@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" @@ -875,6 +879,10 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1047,6 +1055,27 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +cheerio@^0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash.assignin "^4.0.9" + lodash.bind "^4.1.4" + lodash.defaults "^4.0.1" + lodash.filter "^4.4.0" + lodash.flatten "^4.2.0" + lodash.foreach "^4.3.0" + lodash.map "^4.4.0" + lodash.merge "^4.4.0" + lodash.pick "^4.2.1" + lodash.reduce "^4.4.0" + lodash.reject "^4.4.0" + lodash.some "^4.4.0" + chokidar@^1.4.3, chokidar@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" @@ -1230,6 +1259,19 @@ crypto-browserify@^3.11.0: public-encrypt "^4.0.0" randombytes "^2.0.0" +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" + cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.2" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" @@ -1276,6 +1318,12 @@ decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +deep-equal-ident@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz#06f4b89e53710cd6cea4a7781c7a956642de8dc9" + dependencies: + lodash.isequal "^3.0" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -1356,10 +1404,38 @@ doctrine@^2.0.0: esutils "^2.0.2" isarray "^1.0.0" +dom-serializer@0, dom-serializer@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" +domelementtype@1, domelementtype@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domhandler@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + dependencies: + domelementtype "1" + +domutils@1.5.1, domutils@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -1405,6 +1481,43 @@ enhanced-resolve@^3.0.0: object-assign "^4.0.1" tapable "^0.2.5" +entities@^1.1.1, entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +enzyme-matchers@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/enzyme-matchers/-/enzyme-matchers-3.3.0.tgz#cec99edfcb6117ed7d405b3d9b98d1ee3ccdbc47" + dependencies: + deep-equal-ident "^1.1.1" + +enzyme-to-json@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-1.5.1.tgz#e34f4d126bb3f4696ce3800b51f9ed83df708799" + dependencies: + lodash.filter "^4.6.0" + lodash.isnil "^4.0.0" + lodash.isplainobject "^4.0.6" + lodash.omitby "^4.5.0" + lodash.range "^3.2.0" + object-values "^1.0.0" + object.entries "^1.0.3" + +enzyme@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.9.1.tgz#07d5ce691241240fb817bf2c4b18d6e530240df6" + dependencies: + cheerio "^0.22.0" + function.prototype.name "^1.0.0" + is-subset "^0.1.1" + lodash "^4.17.4" + object-is "^1.0.1" + object.assign "^4.0.4" + object.entries "^1.0.4" + object.values "^1.0.4" + prop-types "^15.5.10" + uuid "^3.0.1" + "errno@>=0.1.1 <0.2.0-0", errno@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -1417,7 +1530,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.7.0: +es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" dependencies: @@ -1878,6 +1991,14 @@ function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" +function.prototype.name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.0.tgz#5f523ca64e491a5f95aba80cc1e391080a14482e" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + is-callable "^1.1.2" + gauge@~2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09" @@ -2051,6 +2172,17 @@ html-encoding-sniffer@^1.0.1: dependencies: whatwg-encoding "^1.0.1" +htmlparser2@^3.9.1: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -2154,7 +2286,7 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -is-callable@^1.1.1, is-callable@^1.1.3: +is-callable@^1.1.1, is-callable@^1.1.2, is-callable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" @@ -2267,6 +2399,10 @@ is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -2439,6 +2575,14 @@ jest-environment-node@^19.0.2: jest-mock "^19.0.0" jest-util "^19.0.2" +jest-enzyme@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/jest-enzyme/-/jest-enzyme-3.3.0.tgz#c5b50682e336bf30ed5ff335e89c61a50e64bd04" + dependencies: + "@types/react" "^15.0.22" + enzyme-matchers "^3.3.0" + enzyme-to-json "^1.5.0" + jest-file-exists@^19.0.0: version "19.0.0" resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-19.0.0.tgz#cca2e587a11ec92e24cfeab3f8a94d657f3fceb8" @@ -2738,11 +2882,118 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash._baseisequal@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz#d8025f76339d29342767dcc887ce5cb95a5b51f1" + dependencies: + lodash.isarray "^3.0.0" + lodash.istypedarray "^3.0.0" + lodash.keys "^3.0.0" + +lodash._bindcallback@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash.assignin@^4.0.9: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" + +lodash.bind@^4.1.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" -lodash@^4.0.0, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.3.0: +lodash.defaults@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + +lodash.filter@^4.4.0, lodash.filter@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + +lodash.flatten@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + +lodash.foreach@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.isequal@^3.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-3.0.4.tgz#1c35eb3b6ef0cd1ff51743e3ea3cf7fdffdacb64" + dependencies: + lodash._baseisequal "^3.0.0" + lodash._bindcallback "^3.0.0" + +lodash.isnil@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c" + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.istypedarray@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.map@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + +lodash.merge@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + +lodash.omitby@^4.5.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" + +lodash.pick@^4.2.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + +lodash.range@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.range/-/lodash.range-3.2.0.tgz#f461e588f66683f7eadeade513e38a69a565a15d" + +lodash.reduce@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + +lodash.reject@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + +lodash.some@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -2750,7 +3001,7 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0, loose-envify@^1.1.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -2949,6 +3200,12 @@ npmlog@^4.0.2: gauge "~2.7.1" set-blocking "~2.0.0" +nth-check@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" + dependencies: + boolbase "~1.0.0" + 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" @@ -2969,10 +3226,18 @@ object-hash@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.1.8.tgz#28a659cf987d96a4dabe7860289f3b5326c4a03c" +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + object-keys@^1.0.10, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/object-values/-/object-values-1.0.0.tgz#72af839630119e5b98c3b02bb8c27e3237158105" + object.assign@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" @@ -2981,6 +3246,15 @@ object.assign@^4.0.4: function-bind "^1.1.0" object-keys "^1.0.10" +object.entries@^1.0.3, object.entries@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -2988,6 +3262,15 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" +object.values@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + once@^1.3.0, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3202,7 +3485,14 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.7, prop-types@^15.5.8, prop-types@~15.5.7: +prop-types@^15.5.10, prop-types@^15.5.7, prop-types@~15.5.7: + version "15.5.10" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + +prop-types@^15.5.8: version "15.5.8" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394" dependencies: @@ -3868,7 +4158,7 @@ util@0.10.3, util@^0.10.3: dependencies: inherits "2.0.1" -uuid@^3.0.0: +uuid@^3.0.0, uuid@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"