Skip to content

Commit

Permalink
fix: support shallow route changes
Browse files Browse the repository at this point in the history
* Memoize the i18n client and do not re-create it unless the route or
  locale has changed (fix i18next#1059).

* Let Next's router locale take precedence over initialLocale (fix i18next#1023).

* Bump the minimum version of react-i18next.
  • Loading branch information
skrivanos committed Mar 14, 2021
1 parent c6fc2e3 commit 3b9f891
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 60 deletions.
2 changes: 1 addition & 1 deletion examples/simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"next": "10.0.5",
"next-i18next": "link:../../"
"next-i18next": "link:../.."
},
"devDependencies": {
"prop-types": "^15.7.2"
Expand Down
56 changes: 21 additions & 35 deletions examples/simple/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,17 @@
chalk "^2.0.0"
js-tokens "^4.0.0"

"@babel/runtime@7.12.5", "@babel/runtime@^7.12.0":
"@babel/runtime@7.12.5":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.13.6":
version "7.13.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.7.tgz#d494e39d198ee9ca04f4dcb76d25d9d7a1dc961a"
integrity sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==
"@babel/runtime@^7.12.0", "@babel/runtime@^7.13.6":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"

Expand Down Expand Up @@ -1054,9 +1054,9 @@ copy-descriptor@^0.1.0:
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=

core-js@^3:
version "3.7.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.7.0.tgz#b0a761a02488577afbf97179e4681bf49568520f"
integrity sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==
version "3.9.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae"
integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==

core-util-is@~1.0.0:
version "1.0.2"
Expand Down Expand Up @@ -1870,21 +1870,14 @@ https-proxy-agent@5.0.0:
debug "4"

i18next-fs-backend@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-1.0.7.tgz#00ca4587e306f8948740408389dda73461a5d07f"
integrity sha512-aAZ3rvshe1Zbl6JSCWrWWqbZS5JpmVNG+84YqLcgdYcm9uAxzw4xWxnA/a3044Nm2PKXE62CT+pIZjk7OEYtTw==

i18next@^19.7.0:
version "19.9.1"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.9.1.tgz#7a072b75daf677aa51fd4ce55214f21702af3ffd"
integrity sha512-9Azzyb3DvMJUMd7sPhwVEs6PQcogvdHmLQTjMQ+P+h3XwW4O66/8lgZTmYShgkjPOCqTw4Fwl5LOp/VhZgPo5A==
dependencies:
"@babel/runtime" "^7.12.0"
version "1.1.0"
resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-1.1.0.tgz#3baa63bfb6fd00a331b91d186776cd886b46d2f6"
integrity sha512-QfzfrEYEsLsDC5sZsdSQl5fVYg8I5KrJynnWN7xgSU5yfClbBJ009mtNxUszR0uABQZ8PRr2gj3bN9+RNORBlg==

i18next@^19.8.4:
version "19.8.4"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.8.4.tgz#447718f2a26319b8debdbcc6fbc1a9761be7316b"
integrity sha512-FfVPNWv+felJObeZ6DSXZkj9QM1Ivvh7NcFCgA8XPtJWHz0iXVa9BUy+QY8EPrCLE+vWgDfV/sc96BgXVo6HAA==
i18next@^19.7.0, i18next@^19.8.4:
version "19.9.2"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.9.2.tgz#ea5a124416e3c5ab85fddca2c8e3c3669a8da397"
integrity sha512-0i6cuo6ER6usEOtKajUUDj92zlG+KArFia0857xxiEHAQcUwh/RtOQocui1LPJwunSYT574Pk64aNva1kwtxZg==
dependencies:
"@babel/runtime" "^7.12.0"

Expand Down Expand Up @@ -2485,15 +2478,8 @@ neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2:
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==

"next-i18next@link:../..":
version "8.1.0"
dependencies:
"@types/hoist-non-react-statics" "^3.3.1"
"@types/i18next-fs-backend" "^1.0.0"
core-js "^3"
hoist-non-react-statics "^3.2.0"
i18next "^19.8.4"
i18next-fs-backend "^1.0.7"
react-i18next "^11.8.8"
version "0.0.0"
uid ""

next-tick@~1.0.0:
version "1.0.0"
Expand Down Expand Up @@ -3073,10 +3059,10 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"

react-i18next@^11.8.8:
version "11.8.8"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.8.8.tgz#23d34518c784f2ada7cec41cfe439ac4ae51875c"
integrity sha512-Z8Daifh+FRpcQsCp48mWQViYSlojv0WiL2bf6e9DOzpfVMDaTT6qsYRbHCjLEeDeEioxoaWHMiWu2JPTW3Ni4w==
react-i18next@^11.8.10:
version "11.8.10"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.8.10.tgz#aa64bc20410ee8f660a5b918d53f4e41271edf00"
integrity sha512-ckjNzMjYkmx4fQ8zzuaYTosYN3Co6ebrgCQJzuZCcGFYSR/kGHZzSu0xw9VhtnbjJVKx0gEMV3DLRvzi4xDZUw==
dependencies:
"@babel/runtime" "^7.13.6"
html-parse-stringify2 "^2.0.1"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"hoist-non-react-statics": "^3.2.0",
"i18next": "^19.8.4",
"i18next-fs-backend": "^1.0.7",
"react-i18next": "^11.8.8"
"react-i18next": "^11.8.10"
},
"peerDependencies": {
"next": ">= 10.0.0",
Expand Down
47 changes: 44 additions & 3 deletions src/appWithTranslation.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'
import fs from 'fs'
import { screen, render } from '@testing-library/react'
import { I18nextProvider } from 'react-i18next'
import { useRouter } from 'next/router'
import createClient from './createClient'

import { appWithTranslation } from './appWithTranslation'

Expand All @@ -19,15 +21,17 @@ jest.mock('react-i18next', () => ({
__esmodule: true,
}))

jest.mock('next/router')
jest.mock('./createClient', () => jest.fn())

const DummyApp = appWithTranslation(() => (
<div>Hello world</div>
))

const props = {
const createProps = (locale = 'en') => ({
pageProps: {
_nextI18Next: {
initialLocale: 'en',
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
Expand All @@ -36,7 +40,9 @@ const props = {
},
},
} as any,
} as any
} as any)

const props = createProps()

const renderComponent = () =>
render(
Expand All @@ -50,6 +56,8 @@ describe('appWithTranslation', () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readdirSync as jest.Mock).mockReturnValue([]);
(I18nextProvider as jest.Mock).mockImplementation(DummyI18nextProvider)
const actualCreateClient = jest.requireActual('./createClient');
(createClient as jest.Mock).mockImplementation(actualCreateClient)
})
afterEach(jest.resetAllMocks)

Expand Down Expand Up @@ -124,4 +132,37 @@ describe('appWithTranslation', () => {
expect(fs.readdirSync).toHaveBeenCalledTimes(0)
})

it('should let next router locale take precedence', () => {
(useRouter as jest.Mock).mockReturnValue({ locale: 'de' })
renderComponent()
const [args] = (I18nextProvider as jest.Mock).mock.calls
expect(args[0].i18n.language).toEqual('de')
})

it('does not re-call createClient on re-renders unless locale or props have changed', () => {
(useRouter as jest.Mock).mockReturnValue({ route: '/route' })
const { rerender } = renderComponent()
expect(createClient).toHaveBeenCalledTimes(1)
rerender(
<DummyApp
{...props}
/>
)
expect(createClient).toHaveBeenCalledTimes(1)
const newProps = createProps()
rerender(
<DummyApp
{...newProps}
/>
)
expect(createClient).toHaveBeenCalledTimes(2);
(useRouter as jest.Mock).mockReturnValue({ locale: 'de' })
rerender(
<DummyApp
{...newProps}
/>
)
expect(createClient).toHaveBeenCalledTimes(3)
})

})
35 changes: 20 additions & 15 deletions src/appWithTranslation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import createClient from './createClient'
import { SSRConfig, UserConfig } from './types'

import { i18n as I18NextClient } from 'i18next'
import { useRouter } from 'next/router'
export { Trans, useTranslation, withTranslation } from 'react-i18next'

type AppProps = NextJsAppProps & {
Expand All @@ -22,12 +23,20 @@ export const appWithTranslation = (
configOverride: UserConfig | null = null,
) => {
const AppWithTranslation = (props: AppProps) => {
let i18n: I18NextClient | null = null
let locale = null
const router = useRouter()
const { _nextI18Next } = props.pageProps
const initialLocale = _nextI18Next?.initialLocale || null
const locale = router?.locale || initialLocale

if (props?.pageProps?._nextI18Next) {
let { userConfig } = props.pageProps._nextI18Next
const { initialI18nStore, initialLocale } = props.pageProps._nextI18Next
// Memoize the instance and only re-initialize when either:
// 1. The route changes TODO: probably don't do this after #1049 is solved
// and http backend is used by default.
// 2. Router locale changes
const i18n: I18NextClient | null = useMemo(() => {
if (!locale || !_nextI18Next) return null

let { userConfig } = _nextI18Next
const { initialI18nStore } = _nextI18Next

if (userConfig === null && configOverride === null) {
throw new Error('appWithTranslation was called without a next-i18next config')
Expand All @@ -41,21 +50,17 @@ export const appWithTranslation = (
throw new Error('appWithTranslation was called without config.i18n')
}

locale = initialLocale;

({ i18n } = createClient({
return createClient({
...createConfig({
...userConfig,
lng: initialLocale,
lng: locale,
}),
lng: initialLocale,
lng: locale,
resources: initialI18nStore,
}))
}).i18n
}, [_nextI18Next, locale])

useMemo(() => {
globalI18n = i18n
}, [i18n])
}
globalI18n = i18n

return i18n !== null ? (
<I18nextProvider
Expand Down
17 changes: 12 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1126,13 +1126,20 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.6", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4":
version "7.13.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.7.tgz#d494e39d198ee9ca04f4dcb76d25d9d7a1dc961a"
integrity sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.13.6":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
Expand Down Expand Up @@ -7869,10 +7876,10 @@ react-dom@^17.0.1:
object-assign "^4.1.1"
scheduler "^0.20.1"

react-i18next@^11.8.8:
version "11.8.8"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.8.8.tgz#23d34518c784f2ada7cec41cfe439ac4ae51875c"
integrity sha512-Z8Daifh+FRpcQsCp48mWQViYSlojv0WiL2bf6e9DOzpfVMDaTT6qsYRbHCjLEeDeEioxoaWHMiWu2JPTW3Ni4w==
react-i18next@^11.8.10:
version "11.8.10"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.8.10.tgz#aa64bc20410ee8f660a5b918d53f4e41271edf00"
integrity sha512-ckjNzMjYkmx4fQ8zzuaYTosYN3Co6ebrgCQJzuZCcGFYSR/kGHZzSu0xw9VhtnbjJVKx0gEMV3DLRvzi4xDZUw==
dependencies:
"@babel/runtime" "^7.13.6"
html-parse-stringify2 "^2.0.1"
Expand Down

0 comments on commit 3b9f891

Please sign in to comment.