#remix-i18next client hydration not working

1 messages · Page 1 of 1 (latest)

light cradle
#

Hi, I am struggling to get i18next working properly, and it's probably a mistake by myself, but I can't figure it out.

I have followed the README.md , but for some reason, the client-side hydration seems not to be working properly.

The markup sent by the server is translating everything correctly, but as soon as the hydration starts, the correct translation is converted into the translation key.

When debugging, I saw that i18n.getResource('de', 'common', 'greeting') returns undefined, so I assume that the issue comes from loading the translation files.

This is how my entry.client.ts looks like:

import { RemixBrowser } from '@remix-run/react';
import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-fs-backend';
import { hydrate } from 'react-dom';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import { getInitialNamespaces } from 'remix-i18next';
import i18nConfig from './i18n/i18nConfig';

i18next
    .use(initReactI18next)
    .use(LanguageDetector)
    .use(Backend)
    .init({
        ...i18nConfig,
        ns: getInitialNamespaces(),
        backend: {
            loadPath: '/translations/{{lng}}/{{ns}}.json',
        },
        detection: {
            order: ['htmlTag'],
            caches: [],
        },
    })
    .then(() =>
        hydrate(
            <I18nextProvider i18n={i18next}>
                <RemixBrowser />
            </I18nextProvider>,
            document,
        ),
    );

Does anybody have an idea, of what my config/setup issue is? Happy to share some code when you tell me, which files to check/code to share.

Thanks!

mossy sequoia
#

Hi @light cradle, are you using remix-i18next?

#

nvm, i just saw the imports 🙈

light cradle
#

remix-i18next client hydration not working

mossy sequoia
#

I see you are using hydrate, are you using react 18?

round tulip
#

Will follow this post, i've had the same problem in a project i was working on but didn't have time to find a solution

light cradle
#

@mossy sequoia interestingly not... Its a freshly created remix app with the @latest tag, so I would have assumed it to be using react 18, but its 17.0.2. I probably have to update to react 18 then?

mossy sequoia
#

don't know if it is required, but I just made a fresh remix app (npm init remix) and it installed react 18 for me 🤔

#

should not be required to use react18 though, remix-i18next peerdependency has "react": "^16.8.0 || ^17.0.0 || ^18.0.0", so should work with 17 too...

#

your translation files are in public folder in a translations folder in your app right?

light cradle
#

I just checked and with react 18 you should use hydrateRoot instead of hydrate. So I assume thats not the issue

#

I am just wondering, because server side everything works perfectly fine, but during hydration it seems like the translation files could not be found

mossy sequoia
#

and in i18next.server.ts , entry.client.tsx and entry.server.tsx you are pointing to that translation folder right? cause in the code in readme, those files are in a locales folder instead of translation folder...

light cradle
#

Yes, I updated this everywhere.

i18next.server.ts:

import Backend from 'i18next-fs-backend';
import { resolve } from 'node:path';
import { RemixI18Next } from 'remix-i18next';
import i18nConfig from './i18nConfig';

const i18next = new RemixI18Next({
    detection: {
        supportedLanguages: i18nConfig.supportedLngs,
        fallbackLanguage: i18nConfig.fallbackLng,
    },
    i18next: {
        ...i18nConfig,
        backend: {
            loadPath: resolve('./public/translations/{{lng}}/{{ns}}.json'),
        },
    },
    // The backend you want to use to load the translations
    // Tip: You could pass `resources` to the `i18next` configuration and avoid
    // a backend here
    backend: Backend,
});

export default i18next;
#

entry.client.tsx:

import { RemixBrowser } from '@remix-run/react';
import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-fs-backend';
import { hydrate } from 'react-dom';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import { getInitialNamespaces } from 'remix-i18next';
import i18nConfig from './i18n/i18nConfig';

i18next
    .use(initReactI18next)
    .use(LanguageDetector)
    .use(Backend)
    .init({
        ...i18nConfig,
        ns: getInitialNamespaces(),
        backend: {
            loadPath: '/translations/{{lng}}/{{ns}}.json',
        },
        detection: {
            order: ['htmlTag'],
            caches: [],
        },
    })
    .then(() => {
        hydrate(
            <I18nextProvider i18n={i18next}>
                <RemixBrowser />
            </I18nextProvider>,
            document,
        );
    });
#

entry.server.tsx

import type { EntryContext } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { createInstance } from 'i18next';
import Backend from 'i18next-fs-backend';
import { resolve } from 'node:path';
import { renderToString } from 'react-dom/server';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import i18nConfig from './i18n/i18nConfig';
import i18next from './i18n/i18next.server';

const detectLocale = (request: Request) => {
    const requestURL = new URL(request.url);

    let detectedLocale = requestURL.pathname.split('/')[1];

    if (!i18nConfig.supportedLngs.includes(detectedLocale)) {
        detectedLocale = i18nConfig.fallbackLng;
    }

    return detectedLocale;
};

export default async function handleRequest(
    request: Request,
    responseStatusCode: number,
    responseHeaders: Headers,
    remixContext: EntryContext,
) {
    const instance = createInstance();
    const detectedLocale = detectLocale(request);
    const nameSpace = i18next.getRouteNamespaces(remixContext);

    await instance
        .use(initReactI18next)
        .use(Backend)
        .init({
            ...i18nConfig,
            lng: detectedLocale,
            ns: nameSpace,
            backend: {
                loadPath: resolve('./public/translations/{{lng}}/{{ns}}.json'),
            },
        });

    const markup = renderToString(
        <I18nextProvider i18n={instance}>
            <RemixServer context={remixContext} url={request.url} />
        </I18nextProvider>,
    );

    responseHeaders.set('Content-Type', 'text/html');

    return new Response('<!DOCTYPE html>' + markup, {
        status: responseStatusCode,
        headers: responseHeaders,
    });
}
mossy sequoia
#

hmm

#

that detectLocale stuff is some custom logic with url and the client is using the i18next-browser-languagedetector, so I think it could be possible that the client and server language is not the same?

#

do you still have the issue if you do it like in the docs // Then we could detect locale from the request let lng = await i18next.getLocale(request); // And here we detect what namespaces the routes about to render want to use let ns = i18next.getRouteNamespaces(context);

light cradle
#

Just tested it and its still the same issue

mossy sequoia
light cradle
#

Thank

mossy sequoia
#

hope that one has no errors on your end either?

#

i just discovered that, if you already requested the webpage (and thus cached the resources), when you then add a new language by changing the supportedLngs in i18n.ts and adding a new .json file (and nothing else), i also get a mismatch error (although mine said something like "Hello" doesn't match "Hello" - weird). I need to disable (or clear) cache to get rid of that error... changing those files only, doesnt seem to invalidate the hash in the js send to the client and causes this error to pop 🤔

light cradle
#

I just copied and pasted your code now and it seems to be working

#

I will investigate a little bit further and update the post accordingly

light cradle
#

I can't figure it our I must say... I am back to my original and for some reason it seems to be working, but the cache thing is definitely an issue.

#

@mossy sequoia thanks so much for your help! I really feel super stupid now 😆

mossy sequoia
#

No reason to feel stupid... i think it was just a caching thing if your original is now working too...