22

I am developing a multilanguage application using React, i18next and i18next-browser-languagedetector.

I initialize i18next the following way:

i18n
  .use(LanguageDetector)
  .init({
    lng: localStorage.getItem(I18N_LANGUAGE) || "pt",
    fallbackLng: "pt",
    resources: {
      en: stringsEn,
      pt: stringsPt
    },
    detection: {
      order: ["localStorage", "navigator"],
      lookupQuerystring: "lng",
      lookupLocalStorage: I18N_LANGUAGE,
      caches: ["localStorage"]
    }
  });

export default i18n;

And I have implemented a language selector that just changes the value in the localStorage to what the user chose.

Is this the correct way of doing it?

I ask because even though this works, I feel I am "cheating" by setting localStorage.getItem(I18N_LANGUAGE) || "pt" and that I am not using the language detection as I should.

pteixeira
  • 1,617
  • 3
  • 24
  • 40
  • I'm trying to solve the same problem and found two solutions. First: you can store locale as url parameter (:locale\you_url) like [this](https://alicoding.com/language-code-url-in-react-intl/). The second is your variant - store locale in localStorage or cookies. I would like to do the same as you, but i18next doesn't find keys. Can you write an example or share your code, please? – qwe asd Mar 05 '16 at 14:49
  • We chose to set the locale in the localStorage. What do you mean `i18next` doesn't find the keys? I am using `react-i18next`, by the way. Check this: https://gist.github.com/pteixeira/4a75160ca15e3edf6975 I also have a language selector component that sets the localStorage entry with the value of the language when it initializes, checking for a default value or what the user choses. – pteixeira Mar 06 '16 at 17:01
  • Thanks, it's realy helpful for me. One more question: is there way to change language in runtime? – qwe asd Mar 08 '16 at 19:33
  • What do you mean in runtime? If you're asking while the application is running, in that language selector component I mentioned in the comment above I added an event listener to a select component that has the language options that we support and we set the language by using `i18n.changeLanguage(ev.target.value);` (`i18n`is from `i18next`) and `localStorage.setItem(I18N_LANGUAGE, ev.target.value);`. Hope it was useful :) – pteixeira Mar 09 '16 at 20:33

5 Answers5

9

According to the documentation, you shouldn't need to specify the language yourself:

import i18next from 'i18next';
import LngDetector from 'i18next-browser-languagedetector';

i18next
  .use(LngDetector)
  .init({
    detection: options
  });

And according to this piece of source in i18next, it indeed uses the detection capabilities of the plugin:

if (!lng && this.services.languageDetector) lng = this.services.languageDetector.detect();

Is this the correct way of doing it?

So, no, it isn't . Let the plugin do it's job. :)

Guilherme Rodrigues
  • 2,818
  • 1
  • 17
  • 22
  • 1
    Correct response...if you set language on init the language detector just won't do it's job on detecting. Can be marked as correct answer. Best Jan (aka jamuhl: maintainer of i18next) – jamuhl Nov 21 '16 at 20:28
  • I took a look at the code and refactored it to properly use the plugin. Also marked the answer as accepted. – pteixeira Mar 23 '17 at 17:01
  • I have a doubt here. Of course the plugin does this part perfect, but what about if the user wants to update the default preferred language and not use the one set on the plugin? According to this answer, the user will always load with the default value, but (s)he won't be able to save in local storage the preferred selected language and then relaod the app. How can we achieve this? – Sonhja Oct 20 '21 at 13:38
6

Hopefully this helps someone in the future. The documentation doesn't exactly give you the full picture of how to set up detection, and then I found a closed Github issue where several people were asking a reasonable question, and the maintainers were kinda rude in their responses but also happened to supply a link that should have been in the documentation - but is referenced absolutely no where outside of that Github comment. That example cleared up my issue with a few small adjustments from what the current documentation states to do.

I was then able to get language detection in my url with https:www.domain.com?lng=es as well as when using a browser extension that let me change the browser language.

Heres my working i18n.ts file:

import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'
import XHR from "i18next-http-backend" // <---- add this

import commonDe from './locales/de/common.json'
import commonEn from './locales/en/common.json'
import commonEs from './locales/es/common.json'
import commonFr from './locales/fr/common.json'

const resources = {
  de: { common: commonDe },
  en: { common: commonEn },
  es: { common: commonEs },
  fr: { common: commonFr }
}

const options = {
  order: ['querystring', 'navigator'],
  lookupQuerystring: 'lng'
}

i18n
  .use(XHR) // <---- add this
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    // lng: 'en' // <--- turn off for detection to work
    detection: options,
    resources,
    ns: ['common'],
    defaultNS: 'common',
    fallbackLng: 'en',
    supportedLngs: ['de', 'en', 'es', 'fr'],
    interpolation: {
      escapeValue: false,
    },
    debug: false,
  })

export default i18n

(bonus help - if theres anyone jammed up on this part)

I am working in a Next.js project, and the above file was loaded in the project-root/pages/_app.tsx file like this:

import React from 'react'
import { AppProps } from 'next/app'
import '../i18n/i18n'

import '../public/styles.css'

const TacoFridayApp = ({ Component, pageProps}: AppProps): JSX.Element => {
  
  return <Component {...pageProps} />
}

export default TacoFridayApp
taco_friday
  • 469
  • 7
  • 7
4

I think you are very close. You can just set i18n with fallback language initially. And then after loading saved language information for localstorage or localforage or whatever storage, call i18nInstance.changeLanguage(lng).

sean
  • 1,644
  • 1
  • 15
  • 14
0

@firstdoit:

Good answer with regards to automatic browser language detection. However, don't you think that, it is a better approach of having both the automatic and manual configuration availed to a user.

For instance, if one has a browser set to english, this will be ok for the automatic approach you are suggesting based on the documentation. Should a user change a page language from English to French, this doesn't affect the browser language hence keeping the site only in english because configurations are set to automatically detect browser language.

I, in turn, will give priority to the current page language:

  • Either passed via parameters (/george.php?lang=fr or /fr_FR/george.php)

This will turn be passed as props to my code as a prority as follow

  • var lang = this.props.lang || this.services.languageDetector.detect() || "en";

What's your take?

Joe
  • 1
0

Was wasting a lot of time with this but luckily taco_friday saved my day with his answer. Adding my two cents here:

I basically tried to load data from my localStorage into i18-next. There's a plugin called i18next-localstorage-backend that should facilitate this but I didn't manage to make it run.

But having found inspiration in what taco-friday shared, I could adapt my i18n.ts accordingly:

import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'
import BrowserStorageManager from './services/browserStorageManager'
import loadTranslations from './services/loadTranslations'

const service = new BrowserStorageManager()
loadTranslations()

const resources = {
    'de-CH': service.getItem<any>('de-CH')?.value,
}

i18n.use(LanguageDetector)
    .use(initReactI18next)
    .init({
        debug: false,
        fallbackLng: 'deCH',
        resources,
        interpolation: {
            escapeValue: false, // not needed for react!!
        },
    }) 

The content of browserStorageManager.ts is:

import logSymbols from 'log-symbols'

class BrowserStorageManager {
    public setItem(key: string, value: any): void {
        localStorage.setItem(key, JSON.stringify({ value }))
    }

    public getItem<T>(key: string): T | null {
        const data: string | null = localStorage.getItem(key)
        if (data !== null) {
            return JSON.parse(data)
        }
        console.log(logSymbols.error, ` ${key}-key is empty - aborting ...`)
        return null
    }
}

export default BrowserStorageManager

And finally the structure of a resourceBundle in my localStorage is as follows:

{
    value:{
        translation:{
            key: "value",
            ....
        }
    }
} 

translation is the default namespace so no need to define it in .init

Pascal
  • 77
  • 1
  • 11