Search code examples
react-hooksnext.jsinternationalizationi18nextreact-i18next

React i18next issue during migration to next.js (Error: Text content does not match server-rendered HTML.)


I am trying to migrate from react-router-dom to next.js. I managed to make all major changes but now I am facing an issue with the internationalization of React-i18next. I changed the name of the file from i18n.js to next.config.js but I get this message 'Error: Text content does not match server-rendered HTML'

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

const Languages = ['ar', 'en', 'fr']

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    // lng: 'en',
    react: {
      useSuspense: true,
    },
    // the translations
    // (tip move them in a JSON file and import them,
    // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
    supported: ["en", "fr", "ar"],
    fallbackLng: "en",
    detection: {
      order: ['localStorage', 'htmlTag','cookie' , 'subdomain'],
      // order: ['path', 'cookie', 'htmlTag', 'localStorage', 'subdomain'],
      caches: ['localStorage'],
    },
    debug: false,
    whitelist: Languages,
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    nsSeperator: false,
    keySeperator: false,
    backend: {
      loadPath: '/static/locales/{{lng}}/{{ns}}.json',
    },
  });

export default i18n;

-app.js

import { Provider } from 'react-redux';
import { useStore } from '../store';
import '../styles/globals.css';
import Layout from '../hocs/Layout';
import '../next.config';

function App({Component, pageProps}) {

  const store = useStore(pageProps.initialReduxState);

  return (
    <Provider store={store}>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </Provider>
  );
};

export default App;

loginHeader.js

import React, { Fragment, useEffect } from 'react';
import styles from '../styles/LoginHeader.module.css';
import Link from 'next/link';
import { connect } from 'react-redux';
import { logout } from '../actions/auth';
import SortIcon from '@mui/icons-material/Sort';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import Alert from './Alert';
import Logo from '../assets/images/logoo.png';
import { useTranslation } from "react-i18next";
import { Select, MenuItem } from '@mui/material';
import LanguageIcon from '@mui/icons-material/Language';
import { Link as Scroll } from 'react-scroll';
import Image from 'next/image';

import i18next from 'i18next';
import cookies from 'js-cookie';

const languages = [
  {
    code: 'fr',
    name: 'Français',
    country_code: 'fr'
  },
  {
    code: 'en',
    name : 'English',
    country_code: 'en'
  },
  {
    code: 'ar',
    name: 'العربية',
    country_code: 'ly',
    dir: 'rtl'
  }
]

function LoginHeader({ logout, isAuthenticated }) {

  const currentLanguageCode = cookies.get('i18next') || 'en';
  const currentLanguage = languages.find(l => l.code === currentLanguageCode);
  useEffect (() => {
    document.body.dir = currentLanguage.dir || 'ltr'
    // document.title = t('app_title')
  },[currentLanguage]);

  const { t } = useTranslation()
  
  const guestLinks = () => (
    <Fragment>
      <div className={styles.loginHeader__right}>
        <div className='middle__header__bx'>
    
          <div className={styles.loginHeader__main__btns}>
            <Link className={styles.loginHeader__loginButton} href='/login'><button className={styles.login__btn}>{t('header_login')}</button></Link>
            <Link className={styles.loginHeader__signupButton} href='/signup'><button className={styles.signup__btn}>{t('header_signup')}</button></Link>
          </div> 
        
          <div className={styles.loginHeader__services__dropdown}>
            <Scroll offset={-100}  to='services'><button className={styles['dropdown__btn']+' '+styles['dropdown__services']}>{t('header_services')}<ExpandMoreIcon className={styles.services__expand}/></button></Scroll>
            <div className={styles['dropdown__content']+' '+styles['dropdown__services__content']}>
              <Link className={styles.loginHeader__menuItem} href='/admission'>{t('services_addmissionOffers')}</Link>
              {/* <Link className='loginHeader__menuItem' to='/application-form'>{t('services_forms')}</Link> */}
              <Link className={styles.loginHeader__menuItem} href='/premuim-support'>{t('services_premium')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/visa-assist'>{t('services_visaAssist')}</Link>
            </div>
          </div>
          {/* start of lang box */}
            <Select
              className={styles.loginHeader__select}
              labelId='select-demo'
              id='language-select'
              disableUnderline
              variant='standard'
              IconComponent={LanguageIcon}
            >
              {languages.map(({code, name, country_code}) => 
                <MenuItem
                  className={styles.loginHeader__select__menu}
                  key={country_code}
                >
                  <button 
                    onClick={() => i18next.changeLanguage(code)}
                    className='loginHeader__lang__btn'
                  >
                    {name}
                  </button>
                </MenuItem>
              )}
            </Select>
          {/* end of lang box */}
          <div className={styles['loginHeader__services__dropdown']+' '+styles['sortIcon__bx']}>
            <SortIcon className={styles['dropdown__btn']+' '+styles['loginHeader__sortIcon']}/>
            <div className={styles['dropdown__content']+' '+styles['sortIcon__dropdown']}>
              <Link className={styles.loginHeader__menuItem} href='/premuim-support'>{t('header_dropdown_prem')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/visa-assist'>{t('header_dropdown_visaAssist')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/admission'>{t('header_dropdown_admission')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/request-service'>{t('header_dropdown_requestService')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/contact'>{t('header_dropdown_contact')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/signup'>{t('header_signup')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/login'>{t('header_login')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/guid'>{t('header_dropdown_guide')}</Link>
            </div>
          </div>
        
       </div>
      </div>
    </Fragment>
  );

  const authLinks = () => (
    <Fragment>
      <div className={styles.loginHeader__right}>
        <div className={styles.middle__header__bx}>
            
            <div className={styles.loginHeader__main__btns}>
              <Link className={styles.loginHeader__loginButton} href='/login'><button className={styles.login__btn}>{t('header_login')}</button></Link>
              <Link className={styles.loginHeader__signupButton} href='/signup'><button className={styles.singin__btn}>{t('header_signup')}</button></Link>
            </div> 
          
            <div className={styles.loginHeader__services__dropdown}>
              <Scroll offset={-100}  to='services'><button className={styles['dropdown__btn']+' '+styles['dropdown__services']}>{t('header_services')}<ExpandMoreIcon className={styles.services__expand}/></button></Scroll>
              <div className={styles['dropdown__content']+' '+styles['dropdown__services__content']}>
                <Link className={styles.loginHeader__menuItem} href='/admission'>{t('services_addmissionOffers')}</Link>
                {/* <Link className='loginHeader__menuItem' to='/application-form'>{t('services_forms')}</Link> */}
                <Link className={styles.loginHeader__menuItem} href='/premuim-support'>{t('services_premium')}</Link>
                <Link className={styles.loginHeader__menuItem} href='/visa-assist'>{t('services_visaAssist')}</Link>
              </div>
            </div>
          {/* start of lang box */}
            <Select
              className={styles.loginHeader__select}
              labelId='select-demo'
              id='language-select'
              disableUnderline
              variant='standard'
              IconComponent={LanguageIcon}
            >
              {languages.map(({code, name, country_code}) => 
                <MenuItem
                  key={country_code}
                >
                  <button 
                    onClick={() => i18next.changeLanguage(code)}
                    className='loginHeader__lang__btn'
                  >
                    {name}
                  </button>
                </MenuItem>
              )}
            </Select>
          {/* end of lang box */}
          <div className={styles['loginHeader__services__dropdown']+' '+styles['loggedin__icon__bx']}>
            <button className={styles.dropdown__btn}><SortIcon className={styles['loginHeader__sortIcon']+' '+styles['logedin__sortIcon']}/></button>
            <div className={styles['dropdown__content']+' '+styles['logged__sortIcon__dropdown']}>
              <Link className={styles.loginHeader__menuItem} href='/premuim-support'>{t('header_dropdown_prem')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/admission'>{t('header_dropdown_admission')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/visa-assist'>{t('header_dropdown_visaAssist')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/request-service'>{t('header_dropdown_requestService')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/contact'>{t('header_dropdown_contact')}</Link>
              <Link className={styles.loginHeader__menuItem} href='/guid'>{t('header_dropdown_guide')}</Link>
              <button onClick={logout} className={styles.logout__btn}>{t('header_logout')}</button>
            </div>
          </div>
        </div>
        
      </div>
    </Fragment>
  );

  return (
    <div className={styles.loginHeader}>
      <div className={styles.loginHeader__left}>
        <Link href='/'><Image className={styles.logo} src={Logo} alt='logo'/></Link>  
      </div>
      {isAuthenticated ? authLinks() : guestLinks()}
      <Alert/>
    </div>
  )
};

const mapStateToProps = state => ({
  isAuthenticated: state.auth.isAuthenticated
});

export default connect(mapStateToProps, { logout }) (LoginHeader);


Solution

  • I spent some days following many tutorials and reading. Here is a major note; place next.config.js & related files in the root directory not in src.