-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(i18n): breakout i18n into helpers (#985)
- Loading branch information
Showing
6 changed files
with
167 additions
and
137 deletions.
There are no files selected for viewing
16 changes: 0 additions & 16 deletions
16
src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
src/components/i18n/__tests__/__snapshots__/i18nHelpers.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`I18nHelpers should attempt to perform a component translate: translated component 1`] = `"<div>t(lorem.ipsum, hello world)</div>"`; | ||
|
||
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`] = `"<div>t(lorem.ipsum, {"hello":"world"}, [object Object])</div>"`; | ||
|
||
exports[`I18nHelpers should have specific functions: i18nHelpers 1`] = ` | ||
{ | ||
"EMPTY_CONTEXT": "LOCALE_EMPTY_CONTEXT", | ||
"translate": [Function], | ||
"translateComponent": [Function], | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = () => <div>{translate('lorem.ipsum', { hello: 'world' }, [<span id="test" />])}</div>; | ||
|
||
ExampleComponent.propTypes = {}; | ||
ExampleComponent.defaultProps = {}; | ||
|
||
const component = shallow(<ExampleComponent />); | ||
|
||
expect(component.html()).toMatchSnapshot('translated node'); | ||
}); | ||
|
||
it('should attempt to perform a component translate', () => { | ||
const ExampleComponent = ({ t }) => <div>{t('lorem.ipsum', 'hello world')}</div>; | ||
|
||
ExampleComponent.propTypes = { | ||
t: PropTypes.func | ||
}; | ||
|
||
ExampleComponent.defaultProps = { | ||
t: translate | ||
}; | ||
|
||
const TranslatedComponent = translateComponent(ExampleComponent); | ||
const component = shallow(<TranslatedComponent />); | ||
|
||
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'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. "[<span />, <React.Fragment />]" | ||
* @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 && <Trans i18nKey={updatedTranslateKey} values={updatedValues} components={components} />) || ( | ||
<React.Fragment>t({updatedTranslateKey})</React.Fragment> | ||
) | ||
); | ||
} | ||
|
||
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 }) => ( | ||
<Component | ||
{...props} | ||
t={(i18next.store && translate) || helpers.noopTranslate} | ||
i18n={(i18next.store && i18next) || helpers.noop} | ||
/> | ||
); | ||
|
||
withTranslation.displayName = 'withTranslation'; | ||
return withTranslation; | ||
}; | ||
|
||
const i18nHelpers = { | ||
EMPTY_CONTEXT, | ||
translate, | ||
translateComponent | ||
}; | ||
|
||
export { i18nHelpers as default, i18nHelpers, EMPTY_CONTEXT, translate, translateComponent }; |