diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap index 64c50db1f..6bcf33e48 100644 --- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap +++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap @@ -1,21 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`I18n Component should attempt to perform a component translate: translated component 1`] = `"
t(lorem.ipsum, hello world)
"`; - -exports[`I18n Component should attempt to perform a string replace: translate 1`] = ` -{ - "emptyContext": "t(lorem.ipsum, {"context":" "})", - "emptyPartialContext": "t(lorem.ipsum, {"context":"hello_ "})", - "localeKey": "t(lorem.ipsum)", - "multiContext": "t(lorem.ipsum, {"context":"hello_world"})", - "multiContextWithEmptyValue": "t(lorem.ipsum, {"context":"hello_world"})", - "multiKey": "t([lorem.ipsum,lorem.fallback])", - "placeholder": "t(lorem.ipsum, hello world)", -} -`; - -exports[`I18n Component should attempt to perform translate with a node: translated node 1`] = `"
t(lorem.ipsum, {"hello":"world"}, [object Object])
"`; - exports[`I18n Component should generate a predictable locale key output snapshot: key output 1`] = ` [ { diff --git a/src/components/i18n/__tests__/__snapshots__/i18nHelpers.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18nHelpers.test.js.snap new file mode 100644 index 000000000..df3f252f3 --- /dev/null +++ b/src/components/i18n/__tests__/__snapshots__/i18nHelpers.test.js.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`I18nHelpers should attempt to perform a component translate: translated component 1`] = `"
t(lorem.ipsum, hello world)
"`; + +exports[`I18nHelpers should attempt to perform a string replace: translate 1`] = ` +{ + "emptyContext": "t(lorem.ipsum, {"context":" "})", + "emptyPartialContext": "t(lorem.ipsum, {"context":"hello_ "})", + "localeKey": "t(lorem.ipsum)", + "multiContext": "t(lorem.ipsum, {"context":"hello_world"})", + "multiContextWithEmptyValue": "t(lorem.ipsum, {"context":"hello_world"})", + "multiKey": "t([lorem.ipsum,lorem.fallback])", + "placeholder": "t(lorem.ipsum, hello world)", +} +`; + +exports[`I18nHelpers should attempt to perform translate with a node: translated node 1`] = `"
t(lorem.ipsum, {"hello":"world"}, [object Object])
"`; + +exports[`I18nHelpers should have specific functions: i18nHelpers 1`] = ` +{ + "EMPTY_CONTEXT": "LOCALE_EMPTY_CONTEXT", + "translate": [Function], + "translateComponent": [Function], +} +`; diff --git a/src/components/i18n/__tests__/i18n.test.js b/src/components/i18n/__tests__/i18n.test.js index 104243ded..1378c1edc 100644 --- a/src/components/i18n/__tests__/i18n.test.js +++ b/src/components/i18n/__tests__/i18n.test.js @@ -1,11 +1,9 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import React from 'react'; -import PropTypes from 'prop-types'; -import { shallow } from 'enzyme'; import _get from 'lodash/get'; import enLocales from '../../../../public/locales/en-US.json'; -import { EMPTY_CONTEXT, I18n, translate, translateComponent } from '../i18n'; +import { I18n } from '../i18n'; /** * Get translation keys. @@ -68,54 +66,6 @@ describe('I18n Component', () => { expect(component.html()).toMatchSnapshot('children'); }); - it('should attempt to perform translate with a node', () => { - const ExampleComponent = () =>
{translate('lorem.ipsum', { hello: 'world' }, [])}
; - - ExampleComponent.propTypes = {}; - ExampleComponent.defaultProps = {}; - - const component = shallow(); - - expect(component.html()).toMatchSnapshot('translated node'); - }); - - it('should attempt to perform a component translate', () => { - const ExampleComponent = ({ t }) =>
{t('lorem.ipsum', 'hello world')}
; - - ExampleComponent.propTypes = { - t: PropTypes.func - }; - - ExampleComponent.defaultProps = { - t: translate - }; - - const TranslatedComponent = translateComponent(ExampleComponent); - const component = shallow(); - - expect(component.html()).toMatchSnapshot('translated component'); - }); - - it('should attempt to perform a string replace', () => { - const emptyContext = translate('lorem.ipsum', { context: EMPTY_CONTEXT }); - const emptyPartialContext = translate('lorem.ipsum', { context: ['hello', EMPTY_CONTEXT] }); - const localeKey = translate('lorem.ipsum'); - const placeholder = translate('lorem.ipsum', 'hello world'); - const multiContext = translate('lorem.ipsum', { context: ['hello', 'world'] }); - const multiContextWithEmptyValue = translate('lorem.ipsum', { context: ['hello', undefined, null, '', 'world'] }); - const multiKey = translate(['lorem.ipsum', undefined, null, '', 'lorem.fallback']); - - expect({ - emptyContext, - emptyPartialContext, - localeKey, - placeholder, - multiContext, - multiContextWithEmptyValue, - multiKey - }).toMatchSnapshot('translate'); - }); - it('should generate a predictable locale key output snapshot', () => { expect(getKeys).toMatchSnapshot('key output'); }); diff --git a/src/components/i18n/__tests__/i18nHelpers.test.js b/src/components/i18n/__tests__/i18nHelpers.test.js new file mode 100644 index 000000000..2ecee4b37 --- /dev/null +++ b/src/components/i18n/__tests__/i18nHelpers.test.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import PropTypes from 'prop-types'; +import { i18nHelpers, EMPTY_CONTEXT, translate, translateComponent } from '../i18nHelpers'; + +describe('I18nHelpers', () => { + it('should have specific functions', () => { + expect(i18nHelpers).toMatchSnapshot('i18nHelpers'); + }); + + it('should attempt to perform translate with a node', () => { + const ExampleComponent = () =>
{translate('lorem.ipsum', { hello: 'world' }, [])}
; + + ExampleComponent.propTypes = {}; + ExampleComponent.defaultProps = {}; + + const component = shallow(); + + expect(component.html()).toMatchSnapshot('translated node'); + }); + + it('should attempt to perform a component translate', () => { + const ExampleComponent = ({ t }) =>
{t('lorem.ipsum', 'hello world')}
; + + ExampleComponent.propTypes = { + t: PropTypes.func + }; + + ExampleComponent.defaultProps = { + t: translate + }; + + const TranslatedComponent = translateComponent(ExampleComponent); + const component = shallow(); + + expect(component.html()).toMatchSnapshot('translated component'); + }); + + it('should attempt to perform a string replace', () => { + const emptyContext = translate('lorem.ipsum', { context: EMPTY_CONTEXT }); + const emptyPartialContext = translate('lorem.ipsum', { context: ['hello', EMPTY_CONTEXT] }); + const localeKey = translate('lorem.ipsum'); + const placeholder = translate('lorem.ipsum', 'hello world'); + const multiContext = translate('lorem.ipsum', { context: ['hello', 'world'] }); + const multiContextWithEmptyValue = translate('lorem.ipsum', { context: ['hello', undefined, null, '', 'world'] }); + const multiKey = translate(['lorem.ipsum', undefined, null, '', 'lorem.fallback']); + + expect({ + emptyContext, + emptyPartialContext, + localeKey, + placeholder, + multiContext, + multiContextWithEmptyValue, + multiKey + }).toMatchSnapshot('translate'); + }); +}); diff --git a/src/components/i18n/i18n.js b/src/components/i18n/i18n.js index c4e1a2460..2bad0450d 100644 --- a/src/components/i18n/i18n.js +++ b/src/components/i18n/i18n.js @@ -2,78 +2,10 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import i18next from 'i18next'; import XHR from 'i18next-http-backend'; -import { initReactI18next, Trans } from 'react-i18next'; +import { initReactI18next } from 'react-i18next'; import { useMount } from 'react-use'; import { helpers } from '../../common/helpers'; - -/** - * Check to help provide an empty context. - * - * @type {string} - */ -const EMPTY_CONTEXT = 'LOCALE_EMPTY_CONTEXT'; - -/** - * Apply a string towards a key. Optional replacement values and component/nodes. - * See, https://react.i18next.com/ - * - * @param {string|Array} translateKey A key reference, or an array of a primary key with fallback keys. - * @param {string|object|Array} values A default string if the key can't be found. An object with i18next settings. Or an array of objects (key/value) pairs used to replace string tokes. i.e. "[{ hello: 'world' }]" - * @param {Array} components An array of HTML/React nodes used to replace string tokens. i.e. "[, ]" - * @param {object} options - * @param {string} options.emptyContextValue Check to allow an empty context value. - * @returns {string|React.ReactNode} - */ -const translate = (translateKey, values = null, components, { emptyContextValue = EMPTY_CONTEXT } = {}) => { - const updatedValues = values; - let updatedTranslateKey = translateKey; - - if (Array.isArray(updatedTranslateKey)) { - updatedTranslateKey = updatedTranslateKey.filter(value => typeof value === 'string' && value.length > 0); - } - - if (Array.isArray(updatedValues?.context)) { - updatedValues.context = updatedValues.context - .map(value => (value === emptyContextValue && ' ') || value) - .filter(value => typeof value === 'string' && value.length > 0) - .join('_'); - } else if (updatedValues?.context === emptyContextValue) { - updatedValues.context = ' '; - } - - if (helpers.TEST_MODE) { - return helpers.noopTranslate(updatedTranslateKey, updatedValues, components); - } - - if (components) { - return ( - (i18next.store && ) || ( - t({updatedTranslateKey}) - ) - ); - } - - return (i18next.store && i18next.t(updatedTranslateKey, updatedValues)) || `t([${updatedTranslateKey}])`; -}; - -/** - * Apply string replacements against a component, HOC. - * - * @param {React.ReactNode} Component - * @returns {React.ReactNode} - */ -const translateComponent = Component => { - const withTranslation = ({ ...props }) => ( - - ); - - withTranslation.displayName = 'withTranslation'; - return withTranslation; -}; +import { EMPTY_CONTEXT, translate, translateComponent } from './i18nHelpers'; /** * Load I18n. diff --git a/src/components/i18n/i18nHelpers.js b/src/components/i18n/i18nHelpers.js new file mode 100644 index 000000000..4dcfd8b9e --- /dev/null +++ b/src/components/i18n/i18nHelpers.js @@ -0,0 +1,81 @@ +import React from 'react'; +import i18next from 'i18next'; +import { Trans } from 'react-i18next'; +import { helpers } from '../../common/helpers'; + +/** + * Check to help provide an empty context. + * + * @type {string} + */ +const EMPTY_CONTEXT = 'LOCALE_EMPTY_CONTEXT'; + +/** + * Apply a string towards a key. Optional replacement values and component/nodes. + * See, https://react.i18next.com/ + * + * @param {string|Array} translateKey A key reference, or an array of a primary key with fallback keys. + * @param {string|object|Array} values A default string if the key can't be found. An object with i18next settings. Or an array of objects (key/value) pairs used to replace string tokes. i.e. "[{ hello: 'world' }]" + * @param {Array} components An array of HTML/React nodes used to replace string tokens. i.e. "[, ]" + * @param {object} options + * @param {string} options.emptyContextValue Check to allow an empty context value. + * @returns {string|React.ReactNode} + */ +const translate = (translateKey, values = null, components, { emptyContextValue = EMPTY_CONTEXT } = {}) => { + const updatedValues = values; + let updatedTranslateKey = translateKey; + + if (Array.isArray(updatedTranslateKey)) { + updatedTranslateKey = updatedTranslateKey.filter(value => typeof value === 'string' && value.length > 0); + } + + if (Array.isArray(updatedValues?.context)) { + updatedValues.context = updatedValues.context + .map(value => (value === emptyContextValue && ' ') || value) + .filter(value => typeof value === 'string' && value.length > 0) + .join('_'); + } else if (updatedValues?.context === emptyContextValue) { + updatedValues.context = ' '; + } + + if (helpers.TEST_MODE) { + return helpers.noopTranslate(updatedTranslateKey, updatedValues, components); + } + + if (components) { + return ( + (i18next.store && ) || ( + t({updatedTranslateKey}) + ) + ); + } + + return (i18next.store && i18next.t(updatedTranslateKey, updatedValues)) || `t([${updatedTranslateKey}])`; +}; + +/** + * Apply string replacements against a component, HOC. + * + * @param {React.ReactNode} Component + * @returns {React.ReactNode} + */ +const translateComponent = Component => { + const withTranslation = ({ ...props }) => ( + + ); + + withTranslation.displayName = 'withTranslation'; + return withTranslation; +}; + +const i18nHelpers = { + EMPTY_CONTEXT, + translate, + translateComponent +}; + +export { i18nHelpers as default, i18nHelpers, EMPTY_CONTEXT, translate, translateComponent };