diff --git a/src-docs/src/views/i18n/i18n_interpolation.js b/src-docs/src/views/i18n/i18n_interpolation.js index 4c830cc37c5..1e5c90dfdf8 100644 --- a/src-docs/src/views/i18n/i18n_interpolation.js +++ b/src-docs/src/views/i18n/i18n_interpolation.js @@ -10,7 +10,7 @@ import { } from '../../../../src/components'; export default () => { - const [count, setCount] = useState(1); + const [count, setCount] = useState(0); return ( <> @@ -44,6 +44,35 @@ export default () => { + +

useEuiI18n with function interpolation

+
+

+ {useEuiI18n( + 'euiI18nInterpolation.clickedCount', + ({ count }) => + `Clicked on button ${count} time${count === 1 ? '' : 's'}.`, + { count } + )} +

+ + + + +

EuiI18n with function interpolation

+
+

+ + `Clicked on button ${count} time${count === 1 ? '' : 's'}.` + } + values={{ count }} + /> +

+ + +

useEuiI18n with component interpolation

diff --git a/src/components/i18n/__snapshots__/i18n.test.tsx.snap b/src/components/i18n/__snapshots__/i18n.test.tsx.snap index cd57a4145d1..45c85c487b7 100644 --- a/src/components/i18n/__snapshots__/i18n.test.tsx.snap +++ b/src/components/i18n/__snapshots__/i18n.test.tsx.snap @@ -249,7 +249,19 @@ exports[`EuiI18n mapped tokens mappingFunc calls the mapping function with the s "mapping": Object { "test1": "This is the mapped value.", }, - "mappingFunc": [Function], + "mappingFunc": [MockFunction] { + "calls": Array [ + Array [ + "placeholder", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "PLACEHOLDER", + }, + ], + }, } } > @@ -270,7 +282,19 @@ exports[`EuiI18n reading values from context mappingFunc calls the mapping funct "mapping": Object { "test1": "This is the mapped value.", }, - "mappingFunc": [Function], + "mappingFunc": [MockFunction] { + "calls": Array [ + Array [ + "This is the basic string.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "THIS IS THE BASIC STRING.", + }, + ], + }, } } > @@ -321,6 +345,60 @@ exports[`EuiI18n reading values from context render prop with multiple tokens re `; +exports[`EuiI18n reading values from context render prop with multiple tokens uses the mapping function if one is provided 1`] = ` + + +
+ THIS IS THE FIRST BASIC STRING. + + THIS IS THE SECOND BASIC STRING. +
+
+
+`; + exports[`EuiI18n reading values from context render prop with single token calls a mapped function and renders render prop result to the dom 1`] = ` `; -exports[`EuiI18n useEuiI18n unmapped calls a function and renders the result to the dom 1`] = ` +exports[`EuiI18n useEuiI18n unmapped calls a function that returns JSX and renders the result to the dom 1`] = `

@@ -520,6 +598,34 @@ exports[`EuiI18n useEuiI18n unmapped calls a function and renders the result to `; +exports[`EuiI18n useEuiI18n unmapped calls a function that returns a string and the i18n mapping function 1`] = ` + + +

+ THIS IS A CALLBACK WITH VALUES +
+ + +`; + exports[`EuiI18n useEuiI18n unmapped handles multiple tokens 1`] = `

diff --git a/src/components/i18n/i18n.test.tsx b/src/components/i18n/i18n.test.tsx index 0dd25fc9723..42261c38fe7 100644 --- a/src/components/i18n/i18n.test.tsx +++ b/src/components/i18n/i18n.test.tsx @@ -14,6 +14,10 @@ import { EuiI18n, useEuiI18n } from './i18n'; /* eslint-disable local/i18n */ describe('EuiI18n', () => { + const mockMappingFunc = jest.fn((string: string) => string.toUpperCase()); + + beforeEach(() => jest.clearAllMocks()); + describe('default rendering', () => { describe('rendering to dom', () => { it('renders a basic string to the dom', () => { @@ -210,32 +214,48 @@ describe('EuiI18n', () => { }); describe('render prop with multiple tokens', () => { + const multipleTokens = ( + + {([one, two]: ReactChild[]) => ( +

+ {one} {two} +
+ )} + + ); + const multipleTokensMapping = { + test1: 'This is the first mapped value.', + test2: 'This is the second mapped value.', + }; + it('renders mapped render prop result to the dom', () => { + const component = mount( + + {multipleTokens} + + ); + expect(component).toMatchSnapshot(); + }); + + it('uses the mapping function if one is provided', () => { const component = mount( - - {([one, two]: ReactChild[]) => ( -
- {one} {two} -
- )} -
+ {multipleTokens}
); expect(component).toMatchSnapshot(); + expect(mockMappingFunc).toHaveBeenCalledTimes(2); }); }); @@ -247,7 +267,7 @@ describe('EuiI18n', () => { mapping: { test1: 'This is the mapped value.', }, - mappingFunc: (value: string) => value.toUpperCase(), + mappingFunc: mockMappingFunc, }} > @@ -256,6 +276,7 @@ describe('EuiI18n', () => { ); expect(component).toMatchSnapshot(); + expect(mockMappingFunc).toHaveBeenCalledTimes(1); }); }); }); @@ -300,7 +321,7 @@ describe('EuiI18n', () => { expect(component).toMatchSnapshot(); }); - it('calls a function and renders the result to the dom', () => { + it('calls a function that returns JSX and renders the result to the dom', () => { const values = { type: 'callback', special: 'values' }; const renderCallback = jest.fn(({ type, special }) => (

@@ -315,6 +336,26 @@ describe('EuiI18n', () => { expect(renderCallback).toHaveBeenCalledWith(values); }); + + it('calls a function that returns a string and the i18n mapping function', () => { + const values = { type: 'callback', special: 'values' }; + const renderCallback = jest.fn( + ({ type, special }) => `This is a ${type} with ${special}` + ); + const Component = () => ( +

{useEuiI18n('test', renderCallback, values)}
+ ); + + const component = mount( + + + + ); + + expect(component).toMatchSnapshot(); + expect(renderCallback).toHaveBeenCalledWith(values); + expect(mockMappingFunc).toHaveBeenCalledTimes(1); + }); }); }); @@ -400,7 +441,7 @@ describe('EuiI18n', () => { mapping: { test1: 'This is the mapped value.', }, - mappingFunc: (value: string) => value.toUpperCase(), + mappingFunc: mockMappingFunc, }} > diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx index b8c9a82e4f0..7a1ad5ad041 100644 --- a/src/components/i18n/i18n.tsx +++ b/src/components/i18n/i18n.tsx @@ -61,7 +61,10 @@ function lookupToken< return errorOnMissingValues(token); } // @ts-ignore TypeScript complains that `DEFAULT` doesn't have a call signature but we verified `renderable` is a function - return renderable(values); + const rendered = renderable(values); + return (i18nMappingFunc && typeof rendered === 'string' + ? i18nMappingFunc(rendered) + : rendered) as RESOLVED; } else if (values === undefined || typeof renderable !== 'string') { if (i18nMappingFunc && typeof valueDefault === 'string') { renderable = i18nMappingFunc(valueDefault); @@ -133,6 +136,7 @@ const EuiI18n = < lookupToken({ token, i18nMapping: mapping, + i18nMappingFunc: mappingFunc, valueDefault: props.defaults[idx], render, }) diff --git a/upcoming_changelogs/5748.md b/upcoming_changelogs/5748.md new file mode 100644 index 00000000000..e0917664c2c --- /dev/null +++ b/upcoming_changelogs/5748.md @@ -0,0 +1,3 @@ +**Bug fixes** + +- Fixed `EuiContext.i18n`'s `mappingFunc` not being called for `EuiI18n`s with multiple tokens or function callbacks