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 c3bdb0d
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 37 deletions.
19 changes: 6 additions & 13 deletions examples/simple/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2485,15 +2485,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 +3066,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 has 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 c3bdb0d

Please sign in to comment.