#Remix with i18-next persist user language selection

1 messages · Page 1 of 1 (latest)

steep vine
#

Hi everyone! What would be your recommended approach to persist locale translation selected by the User? My current solution is based on localStorage and useEffect with remix-i18next, but it results in screen flickering, where for a moment I see a default translation

blissful cedar
#

Save it in a cookie or session

steep vine
#

Thank you for a quick reply and great job with helping the community. I tried your suggested approach namely a cookie based solution. But unfortunately the app still is flickering. I am setting a cookie inside an action of language change for my language picker component and it is read properly inside a loader inside my Root component. When I log the locale variable its value is what I would expect and never changes until an action is set. It seems like fallback values are being presented for a moment, because when I removed a fallback translation a key is being shown

blissful cedar
#

did you setup i18next-browser-languagedetector?

#

and are you setting the lang attribute in the <html> tag?

steep vine
#

Yes, I have both set - lang is getting my locale value

worn ember
#

What do you mean by flickering, exactly?

#

Does it render correctly, went kaput, then re-renders everything correctly?

steep vine
#

It renders for a moment with a fallback language english in this case, even though a cookie with another language is set. Then after a short time it's all set. It is a similar problem to setting dark mode from user preference, where the app renders in light mode on init and then re-renders in dark, which is what the user wanted

blissful cedar
#

is your loader returning the correct locale? do you see it in the <html lang>? could you share some repo replicating it?

steep vine
#

<html lang="fr" dir="ltr">

#

that's the setting for a french locale

blissful cedar
#

what's your i18next config?

steep vine
#

export default {
// This is the list of languages your application supports
supportedLngs: ["en", "fr"],
// This is the language you want to use in case
// if the user language is not in the supportedLngs
fallbackLng: "en",
// The default namespace of i18next is "translation", but you can customize it here
defaultNS: "translation",
// Disabling suspense is recommended
react: { useSuspense: false },
};

blissful cedar
#

replace supportedLngs: ["en", "fr"] with supportedLngs: ["fr", "en"]

steep vine
#

no luck

worn ember
#

Set handles properly?

blissful cedar
#

this is most likely a misconfiguration, but it's hard without seeing the code, is it possible to share the code?

steep vine
#

I would have to prepare a separate code base, but won't be able to do it today

#

async function hydrate() {
await i18next
.use(initReactI18next) // Tell i18next to use the react-i18next plugin
.use(LanguageDetector) // Setup a client-side language detector
.use(Backend) // Setup your backend
.init({
...i18n, // spread the configuration
// This function detects the namespaces your routes rendered while SSR use
ns: getInitialNamespaces(),
backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" },
detection: {
// Here only enable htmlTag detection, we'll detect the language only
// server-side with remix-i18next, by using the <html lang> attribute
// we can communicate to the client the language detected server-side
order: ["cookie", "htmlTag"],
// Because we only use htmlTag, there's no reason to cache the language
// on the browser, so we disable it
caches: [],
},
});

startTransition(() => {
hydrateRoot(
document,
<I18nextProvider i18n={i18next}>
<StrictMode>
<RemixBrowser />
</StrictMode>
</I18nextProvider>
);
});
}

blissful cedar
#

order: ["cookie", "htmlTag"], remove cookie here

#

keep only htmlTag

#

you use the cookie server-side in your loader, the i18next-language-detector plugin must read always from htmlTag

steep vine
#

still, it flickers on init

blissful cedar
#

if you're using i18next.getLocale(request) to detect the locale server-side, your can pass your Remix cookie object to RemixI18Next on the detection key

steep vine
#

const i18next = new RemixI18Next({
detection: {
cookie: userPrefs,
supportedLanguages: i18n.supportedLngs,
fallbackLanguage: i18n.fallbackLng,
}

#

import { createCookie } from "@remix-run/node";

export const userPrefs = createCookie("i18next", {
maxAge: 604_800, // one week
});

#

the latter code snippet is my cookie.server.ts

#

Thank you guys, I will come back to this tomorrow as it's very late in my timezone

bright cargo
#

Not sure what you mean by flickering but when using the TFunction from useTranslation hook of react-i18next without suspense you need to wait for the translations to be ready like so:

if (!ready) return null
return <div>{t('greeting')}</div>```

> Not using Suspense you will need to handle the not ready state yourself by eg. render a loading component as long !ready . Not doing so will result in rendering your translations before they loaded which will cause save missing be called although translations exists (just yet not loaded).

https://react.i18next.com/latest/usetranslation-hook
steep vine
#

Thanks, I’ll try with “ready” flag, hopefully adding it somewhere near application root will help

steep vine
#

@bright cargo Waiting for translations helped. I've added a spinner, but it's still a better UX than translations changing from default to selected one, thank you. I thought that, if a locale is set inside a loader and assigned to the document in the root, translation would be available before initial render - maybe it's a misunderstanding on my part

bronze mica
#

@steep vine I've had similar issue, the transltations would be handle just client side.
I've fixed it replacing backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" } everywhere with resources (which is a json imported)

import commonEN from './en/common.json'
import commonIT from './it/common.json'

export const resources = {
  en: { common: commonEN },
  it: { common: commonIT },
}
```
```
  i18next: {
    ...i18n,
    resources,
  },
```
steep vine
#

Thank you, something still seems off, so perhaps my configuration is messed up somewhere. Because reading directly from a file does not seem to fix the original issue. Although I removed the spinner, assuming that was your hint @bronze mica ?

bronze mica
#

In the source code you see the translation or the original text?

steep vine
#

I use translations with const {t} = useTranslation() and then use it inside components as ${t("name", "Name")}. When I open the app I get a moment where I see Name and other default texts and then after a split second it gets translated to a locale from a cookie

bronze mica
#

it means the translation appears just on the client, if you inspect the html you don't have the string translated. it was my same problem but I've fixed as mentioned above