Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translation not working on client component inside layout #1173

Open
zecka opened this issue Nov 25, 2023 · 13 comments
Open

Translation not working on client component inside layout #1173

zecka opened this issue Nov 25, 2023 · 13 comments

Comments

@zecka
Copy link

zecka commented Nov 25, 2023

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)

image

Screenshot of render from client(with javascript browser enabled)

image

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:

  1. This seems to be related to Translations do not work on the first render of Client component when used in Layout next-translate-plugin#75
  2. 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)
  3. 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)
@bmirandan
Copy link

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.

@fuetgeo
Copy link

fuetgeo commented Dec 6, 2023

sounds a lot like #1158 to me.
this issue makes the package unusable for many projects

@acidfernando
Copy link
Contributor

Yeah, it's the same issue as #1158. I created a repro: https://codesandbox.io/p/devbox/relaxed-kapitsa-59xwtj

@i2gor87
Copy link

i2gor87 commented Jan 21, 2024

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

@mikebywaters
Copy link

+1 running into this issue.

@i2gor87
Copy link

i2gor87 commented Jan 23, 2024

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 =(

@acidfernando
Copy link
Contributor

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 😅

@i2gor87
Copy link

i2gor87 commented Jan 25, 2024

@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

@kristian240
Copy link

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}.

@matmkian
Copy link

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 👍

@EdenCoder
Copy link

EdenCoder commented Aug 14, 2024

+1, this is a huge breaking issue for us...

@isBatak
Copy link

isBatak commented Aug 22, 2024

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
Copy link
Owner

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests