react-i18next: Incompatible with hot module reload?

First of all, thank you for this work, šŸ˜„ it really looks a lot easier to use than react-intl which I find a little overkill for a simple app.

I have a translated view that goes like this:

import React from 'react';
import { translate } from 'react-i18next/lib';

const LoginForm = React.createClass({
    render() {
        const t = this.props.t;
        console.log(t); // <-- we should have the translation fn here
        return (
            <div>
                <h3>{ t('Sign in')}</h3>
                {/*...*/}
            </div>
        );
    }
});

export default translate(['login'])(LoginForm);

On first load and manual reload it works as advertised, displaying the translated message. The console.log(t) call returns something like function fixedT(key, options) {...}, but when I modify something on the component and thus triggering the hot reload module (Webpack), I get undefined and an Uncaught TypeError: t is not a function.

Is there any way to make react-i18next compatible with HMR?

Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 34 (15 by maintainers)

Most upvoted comments

@gnapse you mean if you change something in the translation files you want the hmr to reload your page…not sure if that is possible…as those files are not part of the bundle.

I think the initial issue was a reload based on a change in the component…not translation files.

Personally we moved our translation to the service tier locize.com - so we do no manipulations of translations anymore inside the project repository.

Rather sure there is a way to trigger hmr doing some configurations to watch for the public folder too…but personally i prefer doing create-react-app init - so i might have less knowledge about webpack config…eventual this is a good question for stackoverflow?

I got it working. Finally. Via this post: https://github.com/i18next/react-i18next/issues/3, I was mentioned this post: https://github.com/ghengeveld/redux/blob/a68768ff8d6a4fbe7b8f6a06a8cf9b99a54aefb0/docs/recipes/WritingTests.md#testing-decorated-react-components

Basically, because in order to test the undecorated component and not the decorated translate component, I had to export the component using: export class ComponentName extends Component { ... } next to the export default translate('common')(ComponentName) at the bottom. Because I export the undecorated component next to the decorated component, I can import the undecorated component in my unit test using import { FilterRow } from '../components/filterRow'; instead of import FilterRow from '../components/filterRow';

Hey guys, there’s a solution.

Put this after i18next.init call.

if (module.hot) {
  module.hot.accept([assetsModule], () => {
    const res = require([assetsModule]);
    Object
      .keys(res)
      .forEach((lang) => {
        Object
          .keys(res[lang])
          .forEach((namespace) => {
            i18next.addResourceBundle(lang, namespace, res[lang][namespace], true, true );
          })
        ;
      })
    ;

    i18next.emit('loaded');
  });
}

Where [assetsModule] is the module that export the resources, used as resources in the initial option and also for the hot replacement. You can also use i18next-resource-store-loader.

Complete Example:

import i18next from 'i18next'
const resources = require("i18next-resource-store-loader!../assets/i18n/index.js");

i18next
  .init({
    lng: 'en', // set dynamically on build
    fallbackLng: 'en',

    resources: resources,
    debug: true,

    interpolation: {
      escapeValue: false // not needed for react!!
    }
  });

if (module.hot) {
  module.hot.accept("i18next-resource-store-loader!../assets/i18n/index.js", () => {
    const res = require("i18next-resource-store-loader!../assets/i18n/index.js");
    Object
      .keys(res)
      .forEach((lang) => {
        Object
          .keys(res[lang])
          .forEach((namespace) => {
            i18next.addResourceBundle(lang, namespace, res[lang][namespace], true, true );
          })
        ;
      })
    ;

    i18next.emit('loaded');
  });
}

With this, every time you change a value into resources, the hot reloader replace all the namespaces with new values into i18next and emit loaded event.

Doing so, all the react component wrapped with the Translate component, will be re-rendered.

I just ran into this same issue.

I’m using the code as a decorator

@translate(['common'])
class Header extends Component {
  static propTypes = {
    t: PropTypes.func
  };

  render () {
    const { t } = this.props

    return <h1>{t('hello_world')}</h1>
  }
}

Here’s my i18n.js init.

import i18n from 'i18next'
import languages from './strings'

i18n
  .init({
    lng: 'en', // set dynamically on build
    // lngs: Object.keys(languages),
    fallbackLng: 'en',

    ns: ['common'],
    defaultNS: 'common',

    debug: true,

    interpolation: {
      escapeValue: false // not needed for react!!
    }
  })

Object.keys(languages).map(lang => {
  i18n.addResourceBundle(lang, 'common', languages[lang], true)
})

export default i18n

strings/index.js

import en from './en.json'
import de from './de.json'

export { en }
export { de }

export default { en, de }

strings/en.json

{
  "hello_world": "Hello World"
}

And I’m using the json-loader with webpack.