diff --git a/src/appWithTranslation.client.test.tsx b/src/appWithTranslation.client.test.tsx
index 33e6ad70..c809b877 100644
--- a/src/appWithTranslation.client.test.tsx
+++ b/src/appWithTranslation.client.test.tsx
@@ -2,8 +2,10 @@ import React from 'react'
import fs from 'fs'
import { screen, render } from '@testing-library/react'
import { I18nextProvider } from 'react-i18next'
+import createClient from './createClient'
import { appWithTranslation } from './appWithTranslation'
+import { NextRouter } from 'next/router'
jest.mock('fs', () => ({
existsSync: jest.fn(),
@@ -19,15 +21,16 @@ jest.mock('react-i18next', () => ({
__esmodule: true,
}))
+jest.mock('./createClient', () => jest.fn())
const DummyApp = appWithTranslation(() => (
Hello world
))
-const props = {
+const createProps = (locale = 'en', router: Partial = {}) => ({
pageProps: {
_nextI18Next: {
- initialLocale: 'en',
+ initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
@@ -36,9 +39,15 @@ const props = {
},
},
} as any,
-} as any
-
-const renderComponent = () =>
+ router: {
+ locale: locale,
+ route: '/',
+ ...router,
+ },
+} as any)
+
+const defaultRenderProps = createProps()
+const renderComponent = (props = defaultRenderProps) =>
render(
{
(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)
@@ -69,6 +80,7 @@ describe('appWithTranslation', () => {
},
} as any)
const customProps = {
+ ...createProps(),
pageProps: {
_nextI18Next: {
initialLocale: 'en',
@@ -91,6 +103,7 @@ describe('appWithTranslation', () => {
Hello world
))
const customProps = {
+ ...createProps(),
pageProps: {
_nextI18Next: {
initialLocale: 'en',
@@ -124,4 +137,35 @@ describe('appWithTranslation', () => {
expect(fs.readdirSync).toHaveBeenCalledTimes(0)
})
+ it('should use locale from router', () => {
+ renderComponent(createProps('de'))
+ 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', () => {
+ const { rerender } = renderComponent()
+ expect(createClient).toHaveBeenCalledTimes(1)
+ rerender(
+
+ )
+ expect(createClient).toHaveBeenCalledTimes(1)
+ const newProps = createProps()
+ rerender(
+
+ )
+ expect(createClient).toHaveBeenCalledTimes(2)
+ newProps.router.locale = 'de'
+ rerender(
+
+ )
+ expect(createClient).toHaveBeenCalledTimes(3)
+ })
+
})
diff --git a/src/appWithTranslation.server.test.tsx b/src/appWithTranslation.server.test.tsx
index 2f4d8b79..883738d2 100644
--- a/src/appWithTranslation.server.test.tsx
+++ b/src/appWithTranslation.server.test.tsx
@@ -40,6 +40,10 @@ const props = {
},
},
} as any,
+ router: {
+ locale: 'en',
+ route: '/',
+ },
} as any
const renderComponent = () =>
diff --git a/src/appWithTranslation.tsx b/src/appWithTranslation.tsx
index b220dda4..1afae49b 100644
--- a/src/appWithTranslation.tsx
+++ b/src/appWithTranslation.tsx
@@ -22,12 +22,17 @@ export const appWithTranslation = (
configOverride: UserConfig | null = null,
) => {
const AppWithTranslation = (props: AppProps) => {
- let i18n: I18NextClient | null = null
- let locale = null
+ const { _nextI18Next } = props.pageProps
+ const { locale } = props.router
- 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 (non-shallowly)
+ // 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')
@@ -41,21 +46,20 @@ export const appWithTranslation = (
throw new Error('appWithTranslation was called without config.i18n')
}
- locale = initialLocale;
- ({ i18n } = createClient({
+ const instance = createClient({
...createConfig({
...userConfig,
- lng: initialLocale,
+ lng: locale,
}),
- lng: initialLocale,
+ lng: locale,
resources: initialI18nStore,
- }))
- }
+ }).i18n
+
+ globalI18n = instance
- useMemo(() => {
- globalI18n = i18n
- }, [i18n])
+ return instance
+ }, [_nextI18Next, locale])
return i18n !== null ? (
(
const props = {
pageProps: {
_nextI18Next: {
- initialLocale: 'en',
userConfig: {
i18n: {
defaultLocale: 'en',
@@ -27,6 +26,9 @@ const props = {
},
},
} as any,
+ router: {
+ locale: 'en',
+ },
} as any
const renderDummyComponent = () =>
@@ -182,7 +184,6 @@ describe('serverSideTranslations', () => {
initialI18nStore: {
'en-US': {},
},
- initialLocale: 'en-US',
userConfig: {
i18n: {
defaultLocale: 'en-US',
diff --git a/src/serverSideTranslations.ts b/src/serverSideTranslations.ts
index 12978fbb..65fa671c 100644
--- a/src/serverSideTranslations.ts
+++ b/src/serverSideTranslations.ts
@@ -106,7 +106,6 @@ export const serverSideTranslations = async (
return {
_nextI18Next: {
initialI18nStore,
- initialLocale,
userConfig: config.serializeConfig ? userConfig : null,
},
}
diff --git a/src/types.ts b/src/types.ts
index c9d013a4..ace67efe 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -52,7 +52,6 @@ export type CreateClientReturn = {
export type SSRConfig = {
_nextI18Next: {
initialI18nStore: any
- initialLocale: string
userConfig: UserConfig | null
}
}