Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use I18nextProvider with a components library #788

Closed
damienld22 opened this issue Mar 14, 2019 · 30 comments
Closed

Use I18nextProvider with a components library #788

damienld22 opened this issue Mar 14, 2019 · 30 comments

Comments

@damienld22
Copy link

Question about react-i18next

Hello,

I have a question about the utilization of I18nextProvider.

Here is my code :

i18next
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          "Validate": "Validate"
        },
      },
      fr: {
        translation: {
          "Validate": "Valider"
        }
      },
      lng: "fr",
      fallbackLng: "fr",
      interpolation: {
        escapeValue: false
      }
    }
  });

i18next.changeLanguage('fr')

class App extends Component {
  render() {
    return (
     <I18nextProvider i18n={i18next}>
        <Toto/>
    </I18nextProvider>
    );
  }
}

export default App;

And my component Toto is :

class Toto extends Component {
  render() {
    const { t } = this.props;

    return (
      <p>{t('Validate')}</p>
    )
  }
}
const translatedToto = withTranslation()(Toto);
export { translatedToto as Toto };

If my Toto is in the same project that my component App it works well.
But if my Toto component is in an external package, the translation doesn't works and I have the following message in the console :

react-i18next:: You will need pass in an i18next instance by using i18nextReactModule

I am currently developing a components library so I use npm link to make the link between my components library and my test project.

So my question is the following : There is a particular way to use I18nextProvider in this specific case ?
Generally, what is the best practice to use react-i18next with a components library ?

Thanks.

Damien

@jamuhl
Copy link
Member

jamuhl commented Mar 14, 2019

My guess it should work as long your component library does not export some bundles that bundle their own react version -> which in any case will break hooks and context

But I never had the need for that - as most of the components we have in our component libraries are rather plain...where the text in most cases came via props...like eg. <Button>{t('save')}</Button>

If your components are very complex doing something like:

import i18next from 'i18next';
import Backend from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
// import { initReactI18next } from 'react-i18next';

const i18n = i18next.createInstance();
i18n
  // load translation using xhr -> see /public/locales
  // learn more: https://github.com/i18next/i18next-xhr-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  // .use(initReactI18next) // not use as it's a singleton pattern -> use I18nextProvider
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    fallbackLng: 'en',
    debug: true,

    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
  });

export default i18n;

And wrapping your components in that lib with the I18nextProvider might be an option.

@jamuhl
Copy link
Member

jamuhl commented Mar 14, 2019

But you're using

i18next
  .use(initReactI18next)

that should set i18n properly on every usage of react-i18next --> so really seems you bundle two react-i18next

@damienld22
Copy link
Author

Thank you for your quick response it's clearly enjoyable !

So with your response I understand that my approach is not correct...
I will create my components library like you, with text in props.

All is ok for me --> Issue closed.

@wbobeirne
Copy link

I'm hitting this issue as well. I could open a new issue, but it exactly fits this, so I figured I'd add on to this closed issue and maybe have it reopened.

My project topology looks like this; I have a "core" component library (We'll call it library-core) that provides some convenience components, and the translated text for the components. Then I have a bunch of "styled" libraries (We'll call one of them library-style) that use the core components, but wrap them in commonly used component libraries such as Bootstrap, Semantic UI etc. So you can use my component library, but still have it match your project's theme.

So the way it works is that library-core provides a component that wraps the library-style components in a <I18nextProvider />, while also providing some components that are translated. The library-style components then use withRouter to use the translations provided by library-core. However, only library-core's components are being translated and getting the correct props via context, while library-style's components complain with react-i18next:: You will need pass in an i18next instance by using i18nextReactModule. The library-core module has i18next and react-i18next as peer dependencies, and aren't included in its bundle, so I don't think it's an issue with them being double included.

The only way I can think to get around this is to have both libraries do their own <I18nextProvider /> wrapper, and just have library-core export its i18n instance. That seems just a bit less clean than my expected solution though.

I don't want to make the text props as the OP did though, since half the point of using my component library is it provides helpful instruction copy to users on how to complete tasks so you don't have to explain to them how to do something. If you've got any thoughts on this, would love some help! I could also link to the libraries themselves if that would help.

@jamuhl
Copy link
Member

jamuhl commented Apr 20, 2019

@wbobeirne hard to tell what happens and why your code break - only thing i can say it's userland: I18nextProvider is a simple react context (zero magic) - all HOCs, render props and hook take i18n instance from the context if available - else the one instance passed using the i18n.use(...)

Also if you pass me the links i won't have the time to dig through all your code - just not have the time for this -> if you can make a small codesandbox reproducing.

@wbobeirne
Copy link

Yep, that's totally fair. After doing some inspection of my rollup output, it looks like for some reason I was ending up with two outputs of react-i18next which each had their own context variable. This caused them not to share the same i18n, despite being nested. I couldn't quite figure out why this was happening, so I've simply decided to remove react-i18next and export the i18n object from the library-core module itself, and just use that directly.

Less than ideal, but I've had enough rollup configuration hell for one day. But as you said, it looks like this is a configuration issue, not anything inherent to react-i18next. Sorry for wasting your time!

@jamuhl
Copy link
Member

jamuhl commented Apr 21, 2019

If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project -> there are many ways to help this project 🙏

@ndbeyer
Copy link

ndbeyer commented Aug 26, 2019

I am facing a somewhat similar problem. I am working on a project that has three react frontends and only for one of them - the app, i18n-react is instantiated. Also, there is an external component-library that is used by all three frontends. The question is now, how do you optimally translate the texts in the component-library components?

  1. Do you pass the t function from the app components down to the component-library components and leave all the translation work to the component-library components in dependence of the t function
  2. Do you translate all texts in the app components beforehand and pass the translations down to the component-library components

@jamuhl
Copy link
Member

jamuhl commented Aug 26, 2019

@ndbeyer both works...it depends on what you prefer...

@PaulRBerg
Copy link

PaulRBerg commented Nov 10, 2019

Encountered the exact same problem, but I'm not sure I grasped the behaviour of I18nextProvider correctly.

Say I have the following structure:

packages
  components
  app

The components package gets transpiled with its own instance of React (same version though as in app). Does this mean that any context state is not shared between the two packages, hence the i18nobject is different?

@jamuhl
Copy link
Member

jamuhl commented Nov 10, 2019

own instance of react...very bad idea

@PaulRBerg
Copy link

PaulRBerg commented Nov 10, 2019

I'm rather new to React Context. Is it better to just keep components un-transpiled, import them in app and let CRA handle the bundling?

By "better" - I mean whether the i18n would be available in components then.

@jamuhl
Copy link
Member

jamuhl commented Nov 10, 2019

you can transpile them...but exclude react from being included...there at anytime should be only one react instance...that's all

@bnbarak
Copy link

bnbarak commented Apr 14, 2020

You can use <I18nextProvider /> to reference the same instance.

Here is my solution to refactoring the i18nInstance to a separate library (for a lerna monorepo for example)

// utils-lib.js lives outside of the front-end/app project
import { initReactI18next } from "react-i18next";

export const i18nInstance = i18n
  .use(otherThings)
  .use(initReactI18next);

  i18nInstance.init(generateI18nInitOptions(), error => {
    // handle your errors
  });
// App.tsx or App root
import { i18nInstance } from 'utils-lib';
const App = () => 
  <I18nextProvider i18n={i18nInstance}>
     <MyComp />
  </I18nextProvider>
import { useTranslation } from 'react-i18next';

const MyComp = () => {
  const { t, i18n } = useTranslation();
  return <div>{t('tag')}</div>  
}

@MaRuifeng
Copy link

I faced a similar issue when developing locally. The components library package is bridged in via Webpack aliasing hence it inevitably brought in its own instance of react-i18next, which of course is not the initialized one used in the project package. I put below resolve alias in my Webpack config to ensure that it's always the initialized instance being used.

'react-i18next': path.resolve('node_modules/react-i18next'),

I think as @jamuhl pointed out, it's crucial to have only one single INITIALIZED instance of react-i18next.

@eduardoacskimlinks
Copy link

eduardoacskimlinks commented Mar 2, 2022

Following #788 (comment) idea. What we have done is add i18next and react-i18next as peer dependencies, then make sure that our Webpack configuration contains our peerDependencies as externals.

package.json

 "peerDependencies": {
    ...
    "i18next": "21.6.12",
    "react-i18next": "11.15.5"
  },
  "dependencies": {
    "i18next": "21.6.12",
    "react-i18next": "11.15.5"
  }

webpack.config.js

module.exports = {
    ...
    externals: Object.keys(packageJson.peerDependencies),
    ...
}

@frudolph77
Copy link

frudolph77 commented Mar 7, 2022

Hello,

From this comment

I am facing a somewhat similar problem. I am working on a project that has three react frontends and only for one of them - the app, i18n-react is instantiated. Also, there is an external component-library that is used by all three frontends. The question is now, how do you optimally translate the texts in the component-library components?

1. Do you pass the `t function` from the `app components` down to the `component-library components` and leave all the translation work to the `component-library components` in dependence of the `t function`

This is exactly what I have in mind, but I didn't manage to get it working. The component-library should/is provide/providing it's own translation. But currently i18next complains about missing namespace/key.

Is there any detailed example how to perform this task. The documentation about I18nextProvider and how to use it is very limited.

@ndbeyer how did you solve your 'problem'

regards Frank

@OmerShamay
Copy link

own instance of react...very bad idea
@jamuhl
Hey
Just making sure,
Bad idea for having a components package out of the frontend package (multiple react instances) or react-i18-next instances?

@jamuhl
Copy link
Member

jamuhl commented Jun 3, 2022

@OmerShamay having multiple react instances bundled

  • having component packages separated is ok
  • having multiple i18next instances is ok

@eduardoacskimlinks
Copy link

Hello,

From this comment

I am facing a somewhat similar problem. I am working on a project that has three react frontends and only for one of them - the app, i18n-react is instantiated. Also, there is an external component-library that is used by all three frontends. The question is now, how do you optimally translate the texts in the component-library components?

1. Do you pass the `t function` from the `app components` down to the `component-library components` and leave all the translation work to the `component-library components` in dependence of the `t function`

This is exactly what I have in mind, but I didn't manage to get it working. The component-library should/is provide/providing it's own translation. But currently i18next complains about missing namespace/key.

Is there any detailed example how to perform this task. The documentation about I18nextProvider and how to use it is very limited.

@ndbeyer how did you solve your 'problem'

regards Frank

@frudolph77 It's hard without more context on the implementation you followed. Have you tried #788 (comment)?

@el-j
Copy link

el-j commented Mar 7, 2023

hi, sr for reopening, but i am struggling with a similar problem.
i have a module federation app (lerna nx). with a structure like:

| -- library
| -- apps
       | --- containerApp (main menu + nav)
       | --- remoteApp (content in containerApp)
       | --- remoteApp2 (content in containerApp)
      ....

the i18n instance lives in the library and should work shared to the remoteApps. The ContainerApp has the language switch button.

so now i tried to use i18nProviders in the remoteApp and the containerApp to pass the instance from the library.
all the translations get loaded, so this is nice, but a language change does only render and translate the containerApp, not
the remoteApps....

i also tried this approach with not luck
#787
and using it as external always complains about missing react in react-i18next
#788
so no luck...

any idea how i can have this working?

the example provided for a library does not work for me, as my apps are not nested in each others folder structure
https://github.com/i18next/react-i18next/tree/master/example/react-component-lib

@gabycperezdias
Copy link

gabycperezdias commented Apr 17, 2023

@eduardoacskimlinks If I do your suggestion on #788 (comment) my entire app crashes with Uncaught ReferenceError: i18next is not defined, I think your comment and the ones regarding duplicated react and/or react-i18next instances makes total sense, but any idea why would webpack not have react-i18next at all?

Basically, my external package has

 "peerDependencies": {
    ...
    "i18next": "21.6.12",
    "react-i18next": "11.15.5"
  },
  "dependencies": {
    "i18next": "21.6.12",
    "react-i18next": "11.15.5"
  }

And my main package has both as deps (not peers). And then, on webpack I am manually adding those to externals:

externals: {
    'react-i18next': 'react-i18next',
    'i18next': 'i18next'
  },

@manu-058
Copy link

I also had a similar problem, This is how I solved it.

In my case, I was using "rollup" to create the react library, and i was using t("sample_text", "Sample Text") to translate in side my library, but when I consumed my Custom library in a sample project , the text were never translated,

To solve this all I have to do was just provide libraries "react-i18next", "i18next" as external dependency
i mean in rollup.config.js file just update the external with below value

export default {
	entry: 'src/index.js',
	dest: 'bundle.js',
	format: 'cjs',
	external: ["react-i18next", "i18next"]
};

FYI using:
react -18.2.0
rollup - 2.68.0

@eduardoacskimlinks
Copy link

@eduardoacskimlinks If I do your suggestion on #788 (comment) my entire app crashes with Uncaught ReferenceError: i18next is not defined, I think your comment and the ones regarding duplicated react and/or react-i18next instances makes total sense, but any idea why would webpack not have react-i18next at all?

Basically, my external package has

 "peerDependencies": {
    ...
    "i18next": "21.6.12",
    "react-i18next": "11.15.5"
  },
  "dependencies": {
    "i18next": "21.6.12",
    "react-i18next": "11.15.5"
  }

And my main package has both as deps (not peers). And then, on webpack I am manually adding those to externals:

externals: {
    'react-i18next': 'react-i18next',
    'i18next': 'i18next'
  },

Yes, in my case was to fix duplicated instances, as we needed to share instances between packages

@rohilpinto
Copy link

I would like to emphasize that if you are using different versions of the i18n package in your UI and your apps, the shared instance may not work seamlessly. In my case, I had a shared i18n instance, but it failed to detect language changes in the UI. Upon closer inspection, I realized that the package versions were mismatched. Subsequently, I ensured that both the UI and the apps were using the same versions. As a result, the language change is now successfully detected in the UI, and the translations work as intended.

@iDVB
Copy link

iDVB commented May 7, 2024

It's not clear how to get this working for people with UI libs that are "untranspiled" in a monorepo as their own project (nx). We just have ESM components in our ui lib and not bundling them at all. (no rollup etc)
Then we just import that lib into the project with "@company/react-components": "workspace:*",

Then we add lib to transpile list
next.config.js
tried with this excluded and it seems to not make any difference.

...
  transpilePackages: [
    '@company/react-components',
  ],
 ...
  • no rollup, no compile
  • import as ESM lib into app
  • allowing transpile once in app
  • lib contains usages of t(), and <Trans>
  • getting error react-i18next:: You will need to pass in an i18next instance by using initReactI18next on both client and server for one of the lib components.
  • using the following in the lib..
  • lib only lists react, next, and next-i18next as peerDeps
  • provider added using standard appWithTranslation(MyApp) in _app.js
import { Trans, useTranslation } from 'next-i18next'
const { t, i18n, ready } = useTranslation('components')
console.log('FOOTER', { ready, lang: i18n.language })
// FOOTER false, undefined
  • using the following in the app components
import { Trans, useTranslation } from 'next-i18next'
const { t, i18n, ready } = useTranslation('common')
console.log('LAYOUT', { ready, lang: i18n.language })
// LAYOUT true, de

Result
All local app translations are working but all lib components are not translating at all. No context seems to load for components.

next-i18next.config.js

const { DEFAULT_LOCALE, SUPPORTED_LOCALES } = require('@company/i18n')

const i18nConfig = {
  debug: true,
  i18n: {
    defaultLocale: DEFAULT_LOCALE.code,
    locales: SUPPORTED_LOCALES,
  },
  localePath:
    typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/locales',
  nsSeparator: false,
  reloadOnPrerender: true,
}

module.exports = i18nConfig

next.config.js

const { i18n } = require('./next-i18next.config')

const nextConfig = {
  i18n,
  reactStrictMode: false,
  compiler: {
    styledComponents: true,
  },
  webpack: (config) => {
    // this will override the experiments
    config.experiments = { ...config.experiments, topLevelAwait: true }
    config.module.rules.push({
      test: /\.woff(2)?$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: '[name]-[sha512:hash:base64:7].[ext]',
            outputPath: 'static/media/fonts/',
            publicPath: '/_next/static/media/fonts/',
          },
        },
      ],
    })
    return config
  },
}

module.exports = nextConfig

@EduardoAC
Copy link

It's not clear how to get this working for people with UI libs that are "untranspiled" in a monorepo as their own project (nx). We just have ESM components in our ui lib and not bundling them at all. (no rollup etc) Then we just import that lib into the project with "@company/react-components": "workspace:*",

Then we add lib to transpile list next.config.js

...
  transpilePackages: [
    '@company/react-components',
  ],
 ...
  • no rollup, no compile
  • import as ESM lib into app
  • allowing transpile once in app
  • lib contains usages of t(), and <Trans>
  • getting error react-i18next:: You will need to pass in an i18next instance by using initReactI18next on both client and server for one of the lib components.

Result is all local app translations are working but all lib components are not translating at all.

Hi, I cannot give a definitive answer but what we learned on our side. The problem boils down to having the same instance for the lib and the rest of the app. My two cents to help you find a solution is to investigate how you import the t function and what context holds. For instance, in our case, we know the following:

Failing because there is no direct link to the global translation instance

import i18next from "i18next"

Working because in runtime will pick the global translation instance

import { useTranslation } from "react-i18next"

In addition to this specific issue, we encountered another problem related to table cells involving using useTranslation. This prevented us from using the function directly and instead required us to create a contextual object to pass the t function into the component for translation. While I don't recall all the specifics, the function lacked the context at the time of callback when used directly in the cell template, necessitating us to ensure the function was initialised correctly before use.

I hope that helps by giving you some options to explore in your project. Please share your findings, as I would like to try nx in the future and may face similar issues.

@iDVB
Copy link

iDVB commented May 7, 2024

Thanks @EduardoAC your additional issue seems to be the standard issue for us.
Our lib isn't getting the i18n instance context at all?
I've added a bit more context above. Specifically around what we are using in our lib and app to translate.
Would LOVE to understand why specifically "untranspiled/unbundled" code would be any different locally imported vs from an external lib.

@EduardoAC
Copy link

LAYOUT true, de

Hi @iDVB, it's hard to know without the context on how the presentational layer integrates the components together, but it could be well related to the initialisation of the i18n.init(). Some considerations to explore:

  1. Is the footer rendered before the i18n.init() has finished? if this is the case, you can approach it like I have done for browser extensions by introducing a custom hook that will handle initialisation across the different packages/modules/micro-frontend, etc.
  2. I noticed that you use different namespaces in the footer and layout. Have you considered using the common namespace? It's a bit of a long shot, but it might be worth experimenting with to see if it populates the language.

Finally, regarding your question

Would LOVE to understand why specifically "untranspiled/unbundled" code would be any different locally imported vs from an external lib.

Short answer: Yes, depending on the setup.
Longer answer: It could vary depending on the setup, bundler, and configuration. For example, if you're including node_modules in the transpilation process and employing aggressive optimisation or different minification algorithms, the code could break. While this doesn't seem to be the case based on your description, it's still a factor worth considering.

@iDVB
Copy link

iDVB commented May 7, 2024

Thanks @EduardoAC
We're not compiling, building, bundling, or minifying our lib at all.
all of that is just happening in the app as part of standard nextjs build.
Added configs above as well.

Doesn't seem like we're doing anything special... so it seems like there is just no way to get external libs to use the same instance in the app?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests