Translation not working on client component inside layout
Opened this issue ยท 13 comments
What version of this package are you using?
next-translate: ^2.6.2
next-translate-plugin: ^2.6.2
next: 14.0.3
What operating system, Node.js, and npm version?
node: 18.17.1
npm: 9.6.7
yarn: 1.22.19
What happened?
I'm using app router on nextjs
In a client component used on a layout, i use useTranslation
. On server rende, all translations is correct. But on client side the translation is not loaded.
The same component work well when used on page.tsx
// src/components/TestClient.tsx
'use client';
import useTranslation from 'next-translate/useTranslation';
export const TestClient = () => {
const { t } = useTranslation('common');
return <div>This is client component, translation value: {t('test')}</div>;
};
// src/components/TestServer.tsx
import useTranslation from "next-translate/useTranslation";
export const TestServer = () => {
const { t } = useTranslation("common");
return <div>This is server component, translation value: {t("test")}</div>;
};
// src/app/layout.tsx
import { TestClient } from "@/components/TestClient";
import { TestServer } from "@/components/TestServer";
export default function RootLayout({ children}: {children: React.ReactNode;}) {
return (
<html lang="en">
<body>
<h2>Inside LAYOUT</h2>
<TestClient />
<TestServer />
{children}
</body>
</html>
);
}
// src/app/page.tsx
import { TestClient } from "@/components/TestClient";
import { TestServer } from "@/components/TestServer";
const Page= () => {
return (
<div>
<h2>Inside PAGE</h2>
<TestServer />
<TestClient />
</div>
);
};
Screenshot of render from server (with javascript browser disabled)
Screenshot of render from client(with javascript browser enabled)
How reproduce?
Github repository for this issue: https://github.com/zeckaissue/next-translate-issue-client-on-layout
Codesandbox for this issue: https://codesandbox.io/p/github/zeckaissue/next-translate-issue-client-on-layout/main?file=%2Fsrc%2Fapp%2Flayout.tsx%3A21%2C23&workspaceId=08c0217d-10c7-486d-a55b-8417490cf07b
EDIT:
- This seems to be related to aralroca/next-translate-plugin#75
- This issue doesn't appear on next13 with next-translate-plugin 2.4.4 (Check this branch of my reproduction repository: https://github.com/zeckaissue/next-translate-issue-client-on-layout/tree/next13)
- This still work with next-translate-plugin 2.6.2 on next 13 (https://github.com/zeckaissue/next-translate-issue-client-on-layout/tree/next13-next-translate-plugin-2.6.2)
My team encountered the same issue just yesterday. We even did a test to check this out and we've been able to replicate it. It seems to be a problem with the last version only 'cause we downgraded and it works client side.
sounds a lot like #1158 to me.
this issue makes the package unusable for many projects
Yeah, it's the same issue as #1158. I created a repro: https://codesandbox.io/p/devbox/relaxed-kapitsa-59xwtj
Any updates on this? Because I tend to use one layout tsx for almost all pages where I need consistent layout and this breaks things for me
+1 running into this issue.
I might have a solution here, but it might not be suitable for everyone. For me it does only half the work, because I want to use dynamic language changes via queryParams and this solution does not address this issue (but the content is translated and header on reload is translated as well). I hope someone can provide a more suited solution
Diving into the next.js docs, I found out that
layouts preserve state, remain interactive, and do not re-render.
but there is another option - templates which
are similar to layouts in that they wrap each child layout or page. Unlike layouts that persist across routes and maintain state, templates create a new instance for each of their children on navigation.
That being said, if you have this structure
// src/components/translated-component.tsx
'use client';
import useTranslation from 'next-translate/useTranslation';
export const TranslatedComponent = () => {
const { t } = useTranslation('common');
return <div>This is component that needs translation, translation value: {t('test')}</div>;
};
// src/app/layout.tsx
import type {Metadata} from 'next'
import {ReactNode} from "react";
import TranslatedComponent from "@/components/translated-component";
import './globals.css'
export const metadata: Metadata = {
title: 'Website title',
description: 'Website description',
}
export default function LocaleLayout({ children } : { children: ReactNode }) {
return (
<html lang='en' suppressHydrationWarning>
<body>
<TranslatedComponent />
{children}
</body>
</html>
)
}
and you run into issue mentioned before, you can take out TranslatedComponent
from layout.tsx
(or js, or jsx) and put it into new file template.tsx
. But only moving the component won't solve the issue, you need to use useTranslation
inside template.tsx and pass translated strings as props to problematic element:
// src/components/translated-component.tsx
export const TranslatedComponent = ({value} : {value:string}) => {
return <div>This is component that needs translation, translation value: {value}</div>;
};
// src/app/layout.tsx
import type {Metadata} from 'next'
import {ReactNode} from "react";
import './globals.css'
export const metadata: Metadata = {
title: 'Website title',
description: 'Website description',
}
export default function LocaleLayout({ children } : { children: ReactNode }) {
return (
<html lang='en' suppressHydrationWarning>
<body>
// <TranslatedComponent /> // remove this line
{children}
</body>
</html>
)
}
// src/app/template.tsx
import TranslatedComponent from "@/components/translated-component";
import useTranslation from 'next-translate/useTranslation';
export default function Template({children}: { children: React.ReactNode }) {
const { t } = useTranslation('common');
return <div>
<TranslatedComponent value{t('test')}/>
{children}
<Footer/>
</div>
}
which would result in following nested structure:
<Layout>
<Template>{children}</Template>
</Layout>
Basically template starts doing the work of layout, but with useEffect and other client functions
More info in NextJS documentation
I also including link to corrected sandbox that I forked from #1173 (comment)
P.S. I'm not very experienced with next.js and typescript, so if there are any hidden problems with using template file over layout file, I really would like to know. But this setup is working for me so far.
P.S.S Before fix I just put my dynamic components in page. But that's weird solution =(
That works for my use case. I think this is not an error with next-translate but with how nextJS works. I think this issue should be closed with this fix
Also, it would be interesting to have this knowledge somewhere in the docs, I think people will be running into this quite frequently
I'm not sure where to add it, though ๐
@acidfernando This still doesn't solve the issue for people who would like to use the dynamic translation by lang={locale}
method. For some reason, the template.tsx
is updated only on page transitions, changing translation on the page translates only content, but not strings in the component in the template.tsx
. You can see that behaviour in my sandbox - clicking on english / french buttons changes translations for body component, but not the component inside template
One thing that might be helpful here is the fact that I experience this when I render the children
behind a client-only
gurad (we use this pattern https://github.com/gfmio/react-client-only)
When component is not wrapped with client only guard the translation is loaded (but only if the client component is rendered after the {children}
.
Do we have some updates regarding this issue? It's pretty huge. I personally had to use another lib to manage translations because of this :-( It's sad because I love this lib ๐
+1, this is a huge breaking issue for us...
This might be a possible solution:
// src/app/layout.tsx
import { TestClient } from "@/components/TestClient";
import { TestServer } from "@/components/TestServer";
import I18nProvider from 'next-translate/AppDirI18nProvider';
export default function RootLayout({ children}: {children: React.ReactNode;}) {
const { config, lang, namespaces } = globalThis.__NEXT_TRANSLATE__;
return (
<html lang="en">
<body>
<I18nProvider
lang={lang}
namespaces={namespaces}
config={JSON.parse(JSON.stringify(config))}
>
<h2>Inside LAYOUT</h2>
<TestClient />
<TestServer />
{children}
</I18nProvider>
</body>
</html>
);
}
Make sure to import Provider like this import I18nProvider from 'next-translate/AppDirI18nProvider';
because next-translate-plugin
also imports this module here https://github.com/aralroca/next-translate-plugin/blob/99c1faefcd7167a7caa309f2affc7d0d807a76e4/src/templateAppDir.ts#L95
globalThis.__NEXT_TRANSLATE__
is available on the server side because next-translate-plugin
actually wrap your component source code with some stuff that is doing side-effects
:
https://github.com/aralroca/next-translate-plugin/blob/99c1faefcd7167a7caa309f2affc7d0d807a76e4/src/templateAppDir.ts#L127
Additionally, AppDirI18nProvider
name is kinda wrong because this component is not a provider, it just doing a side-effect
in the render, which seems pretty wrong.
export default function AppDirI18nProvider({
lang,
namespaces = {},
config,
children,
}: AppDirI18nProviderProps) {
globalThis.__NEXT_TRANSLATE__ = { lang, namespaces, config } // this is a side-effect
// It return children and avoid re-renders and also allow children to be RSC (React Server Components)
return children
}
What might also work is to use Script
component like this:
export default function RootLayout({ children}: {children: React.ReactNode;}) {
const { config, lang, namespaces } = globalThis.__NEXT_TRANSLATE__;
return (
<html lang="en">
<head>
<Script id={lang}>
{`globalThis.__NEXT_TRANSLATE__ = ${{ lang, namespaces, config }}`}
</Script>
</head>
<body>
<h2>Inside LAYOUT</h2>
<TestClient />
<TestServer />
{children}
</body>
</html>
);
}
@aralroca What do you think of this Script
solution?
Script
also have strategy settings to assure it's executed on time https://nextjs.org/docs/app/building-your-application/optimizing/scripts#strategy
@aralroca What do you think of this
Script
solution?Script
also have strategy settings to assure it's executed on time https://nextjs.org/docs/app/building-your-application/optimizing/scripts#strategy
Good to know it @isBatak , probably we can add it internally and solve this AppDirI18nProvider
wrong implementation. Lately I am quite busy, I will try to dedicate some time to it when I can, otherwise any PR will be welcome.