From cf07b332413fdd361a31e40126a7d64b9d98ee85 Mon Sep 17 00:00:00 2001 From: Ryan Wilson-Perkin Date: Thu, 14 Sep 2023 10:22:10 -0400 Subject: [PATCH] Only insert key on react elements (#188) * Only format count/ordinal as number if they are one * changeset * Declare explicit peer dep on React We need to use utilities such as cloneElement and isValidElement * Clone element when necessary to inject key We're re-creating any object that doesn't have a key and injecting a key into it, which is causing non-react elements such as arrays and `expect.any` style matchers to be mutated into a shape that can't then be used. Arrays are valid as children and should be preserved, and expect.any is useful for matching translations. We should be using React's cloneElement when we need to mutate an element in this way so that it can still be tested against when injecting a key prop into it. * changeset --- .changeset/breezy-poets-count.md | 5 ++++ .changeset/breezy-pugs-provide.md | 5 ++++ package.json | 3 +++ src/utils.js | 7 +++--- .../shopify_format_with_react_i18next.spec.js | 23 +++++++++++++++++++ 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 .changeset/breezy-poets-count.md create mode 100644 .changeset/breezy-pugs-provide.md diff --git a/.changeset/breezy-poets-count.md b/.changeset/breezy-poets-count.md new file mode 100644 index 0000000..c3b113a --- /dev/null +++ b/.changeset/breezy-poets-count.md @@ -0,0 +1,5 @@ +--- +'@shopify/i18next-shopify': patch +--- + +Don't break objects when inserting key prop diff --git a/.changeset/breezy-pugs-provide.md b/.changeset/breezy-pugs-provide.md new file mode 100644 index 0000000..a155c52 --- /dev/null +++ b/.changeset/breezy-pugs-provide.md @@ -0,0 +1,5 @@ +--- +'@shopify/i18next-shopify': patch +--- + +Only format count/ordinal as number if they are one diff --git a/package.json b/package.json index 9e0fd3c..094ff08 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "type": "git", "url": "https://github.com/Shopify/i18next-shopify" }, + "peerDependencies": { + "react": "*" + }, "devDependencies": { "@babel/cli": "^7.15.0", "@babel/core": "^7.15.0", diff --git a/src/utils.js b/src/utils.js index 4596005..8644306 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,5 @@ +const {isValidElement, cloneElement} = require('react'); + const arr = []; const each = arr.forEach; @@ -34,10 +36,9 @@ export function replaceValue(interpolated, pattern, replacement) { if (split.length !== 1 && typeof replacement === 'object') { // Return array w/ the replacement - // React elements within arrays need a key prop - if (!replacement.key) { + if (!replacement.key && isValidElement(replacement)) { // eslint-disable-next-line no-param-reassign - replacement = {...replacement, key: pattern.toString()}; + replacement = cloneElement(replacement, {key: pattern.toString()}); } return [split[0], replacement, split[1]].flat(); diff --git a/test/shopify_format_with_react_i18next.spec.js b/test/shopify_format_with_react_i18next.spec.js index 0196c44..d0885fa 100644 --- a/test/shopify_format_with_react_i18next.spec.js +++ b/test/shopify_format_with_react_i18next.spec.js @@ -207,6 +207,29 @@ describe('shopify format with react-i18next (t)', () => { informal_greeting: 'Sup Joe', }); }); + + it('matches expect.anything() in interpolated variables', () => { + const {result} = renderHook(() => useTranslation('translation')); + const {t} = result.current; + + expect( + t('string_with_single_mustache', { + name: Joe, + }), + ).toStrictEqual( + t('string_with_single_mustache', {name: expect.anything()}), + ); + }); + + it('accepts arrays as interpolated variables', () => { + const {result} = renderHook(() => useTranslation('translation')); + const {t} = result.current; + expect( + t('string_with_single_mustache', { + name: ['Joe', 'Jane'], + }), + ).toStrictEqual(['Hello ', 'Joe', 'Jane', '!']); + }); }); describe('with react-i18next (Trans)', () => {