12

I have a question regarding the multi-language support for complex React application. All examples and documentation are based on "flat" application without nested/child components.

How to approach data nested like this:

<i18n>
    <App>
        translate('base')(
          <Base>
              <Child1 />
              <Child2 />
              {t('Hello')}
          </Base>
        )
    </App>
</i18n>

Should I wrap every child component with translate HOC?
Is there some other way to pass the translation function down to the child components?

Community
  • 1
  • 1
J33nn
  • 3,034
  • 5
  • 30
  • 46

4 Answers4

16

I had the same problem not long ago. There are 4 solutions I found for this.

  1. Passing t down to every component. This one is very annoying and leads to a lot of bugs because I was forgetting to pass it down all the time.

  2. Using the NamespacesConsumer context provided by react-i18next. This one is also really annoying and the syntax is sometimes too weird and repetitive. This can also be bad for performance because components might re-render often.

  3. Using the withNamespaces HOC provided by react-i18next, this is a great option, it's easy to read and doesn't pollute your code with translation syntax. It's also more efficient than the previous two options.

  4. This one is my favorite solution, I end up using i18next directly because you have access to t() everywhere out of the box, without additional code.

If you want to keep react-i18next, I would recommend you to use a HOC, it's way easier to debug, test and develop. But honestly, i18next is doing a better job in my own opinion. I initially use react-i18next because I thought it was the react way to go, but it is just a pain to use it, react-i18next has a lot of bugs and it's way more code to write. Using i18next is simple as this

import i18next from 'i18next';

i18next.t('parentKey.childKey');
vsync
  • 118,978
  • 58
  • 307
  • 400
Vincent D'amour
  • 3,746
  • 1
  • 27
  • 39
  • 2
    I think one problem with using i18next directly is your components won't rerender when language is changed. – Hendy Irawan Jan 18 '19 at 19:37
  • 2
    That's true, but what I usually do is reloading the page with the new language. It's not that bad and the app without react-i18next is much more faster, easy to test, easy to debug and the code is cleaner. Most people will change language only one time, so using a complex library only to support this feature is a little bit overkill in my own opinion. Give it a try, this is the best advice I can give you. – Vincent D'amour Jan 19 '19 at 03:31
  • 2
    makes sense. I suggest you mention the `withNamespaces` HOC provided by react-i18next, instead of "a HOC". – Hendy Irawan Jan 19 '19 at 03:37
  • 1
    I also got hit by this: in `form-redux` `validate()`, I need to show localized errors. Problem is `validate()` doesn't have access to `Component.props` (CMIIW). So I had to `import i18n from "i18next"` anyway and user `i18n.t()` directly inside `validate()`. ;-) – Hendy Irawan Jan 19 '19 at 05:38
  • 1
    What fixed it for me with my child components was adding the namespace with the key `t(ns:key)` exactly like you have in your code example! Thanks – Stephane Jan 29 '22 at 15:03
2

You also can pass it down via regular props from outer component.

Like having container components which gets wrapped with translate hoc and the inner components you just pass the t function via props.

jamuhl
  • 4,352
  • 2
  • 25
  • 31
  • Well, I'm aware of that :) Question is, what is the best approach for this ;) – J33nn Aug 03 '18 at 11:36
  • 2
    That is the worst possible thing to do; **manually** passing props down to endless levels. I wouldn't recommend this for anything more than 2 levels, and no app is just 2 levels down. especially with translations – vsync Aug 20 '19 at 07:25
1

The best approach would be to wrap your main component with a React.Context and pass the t prop as a context and have it accessible in each of your nested child components.

I am using localization as well in my application like this.

Pros:

  • Doesn't destroy child hierarchy when every component is wrapped with a hOC
  • In the future if you decide to chose another library for localization, you can just change the main Provider component of that Context and it will update the implementation through out your application.
  • Since the translations are loaded initially, your context won't update & re-render issue won't come again and again.
Adeel Imran
  • 13,166
  • 8
  • 62
  • 77
  • Even if the value of t doesn't change, the context will re-render the components inside. That's really annoying from react. You need to specify shouldComponentUpdate or to use PureComponent, but sometimes you only render a stateless function component or a component from a library that you can modify. I find it annoying to re-render a lot of components only to translate strings. But this is a fine solution for most projects, it's important to note that performance issue or rendering bugs might be caused by the context. – Vincent D'amour Jan 23 '19 at 13:12
  • Wrong sir, Context will not continuously update your tree unless changed otherwise. react-router & redux have been using context underneath their library from the get go start. – Adeel Imran Jan 24 '19 at 05:44
  • Yes, true, but I was talking about the way react-i18next handles it. why-did-you-update was triggering all the time when I was using react-i18next because the context you get from the library uses functions that are not bonded. To avoid that, you need to create your own context, which is annoying. – Vincent D'amour Jan 24 '19 at 13:49
  • It will only update when we use react-i18next changeLanguage method, and change all the localization in the application. Which I think is the right behavior here because we want all our children to render which are using localizations (which I think are almost all components) The rest can be `memo` or `Pure.Component` – Adeel Imran Jan 25 '19 at 04:43
  • I'm not sure we are understanding each other here. But this is basically what I'm saying. The way react-i18next handles the props of the context is buggy. This cause a lot of re-render even if the language didn't change. Like you said, to avoid all those re-render, you need to use a memoize function or to convert the components to a PureComponent, which is annoying when you can have a SFC. For me, having to convert a stateless component to a pure component only to avoid re-renders is a big problem. – Vincent D'amour Jan 25 '19 at 05:33
1

If you are using hooks, and not classes (like me) when coding your React components, then you can use the useTranslation hook:

import React from 'react';
import { useTranslation } from 'react-i18next';

export function MyComponent() {
  const { t, i18n } = useTranslation();
  // or const [t, i18n] = useTranslation();

  return <p>{t('my translated text')}</p>
}

This, just like the withTranslation wrapper, requires importing (the hook) in every file which uses translations.

vsync
  • 118,978
  • 58
  • 307
  • 400