i18next / next-i18next

The easiest way to translate your NextJs apps.
https://next.i18next.com
MIT License
5.48k stars 760 forks source link

Next.js 10 #869

Closed martpie closed 3 years ago

martpie commented 3 years ago

Is your feature request related to a problem? Please describe.

Next.js is just out, and introduces i18n routing primitives

https://nextjs.org/docs/advanced-features/i18n-routing

Describe the solution you'd like

I think a lot of the features of next-i18next can be dropped, so next-i18next really just focuses on the i18next integration.

Describe alternatives you've considered

n/a

Additional context

n/a

isaachinman commented 3 years ago

Yep, have been waiting for it to land. We'll want to rewrite so that the main concern of next-i18next is handling data management for SSR apps.

Would be very happy to have input from collaborators!

kachkaev commented 3 years ago

@isaachinman nice! Could you elaborate a bit on what you mean by data management in this context?

isaachinman commented 3 years ago

So, the NextJs core as of v10 intends to take all responsibility for locale routing. That means we can remove a lot of functionality from next-i18next.

What NextJs v10 does not do, is handle any sort of actual data management for users.

In the case of full SSG apps, you can, in theory, get away with passing your localisation data as props per page, kind of like this example. However, that means you'll be sending all of your translation data down to every single page.

If i18n routing is being taken care of by NextJs internally, all that's left for a package like next-i18next to do is handle the management of actual translation content. This is basically a harmony/synchronisation of i18next backend packages, and NextJs opinionated patterns. For example, we'll still need to hook into NextJs Router events and load in necessary namespaces on route change, etc.

mercteil commented 3 years ago

Very exciting release specifically in regards to internationalization. I am looking forward to be able to use SSG or at least gerServerSideProps to be able to load namespacesRequired and to abandon getInitialProps. However the features I see provided by Vercal at this time are language detection an an early intl libs support.

Few things are still not clear to me:

Basically:

If i18n routing is being taken care of by NextJs internally, all that's left for a package like next-i18next to do is handle the management of actual translation content. This is basically a harmony/synchronisation of i18next backend packages, and NextJs opinionated patterns. For example, we'll still need to hook into NextJs Router events and load in necessary namespaces on route change, etc.

SalahAdDin commented 3 years ago

Does NextJS handle localization files? If it is using react-i18n and i18n-next it is still possible to use their hook to handle switching languages.

isaachinman commented 3 years ago

NextJs only handles the routing. Handling localisation files, splitting translations across pages, etc, will be the new goal of packages like next-i18next.

I hope to spend some hours this weekend taking a deep look at a rewrite for next-i18next to simplify things dramatically.

underfisk commented 3 years ago

After upgrade to next10, i got react hooks issue that the context of Next18n is rendering more hooks than before, it doesn't always happen but sometimes i got this annoying error saying the context of next-i18next is rendering more or fewer hooks which is kinda odd to debug Anyone experiencing the same? @isaachinman any idea of what's going on or what might be causing that?

MathiasKandelborg commented 3 years ago

@isaachinman Lmk if I can help. I've done some things to load translations specified with namespaces, with SSG.

https://github.com/MathiasKandelborg/TRUE-framework/tree/master/util/i18n

I have time to contribute so I might take a look myself. Would love to contribute, so let me know if there's anything I can begin with.

isaachinman commented 3 years ago

@MathiasKandelborg I am starting to look into this now, but unfortunately only have about an hour today.

Overview

I will write my thoughts below, to give any potential contributors a chance to work on this without my schedule blocking development. I understand that this is time-sensitive in a way, and would greatly appreciate help anyone might be able to offer.

In general, the future version of next-i18next should be a dramatically simpler codebase to maintain and reason about.

It should be said that NextJs's i18n routing implementation has less features than next-i18next's current implementation. Whether or not that is a bad thing is entirely subjective, it's just down to certain decisions the Vercel team has made. For example, if people want to enforce locale subpaths, they will need to write their own redirects in next.config.js, etc.

As I wrote above, the main purpose of next-i18next moving forward will be the handling of namespaces, i18next-fs-backend, I18nextProvider, and so on.

Software Approach

The next-i18next package will just export functions now, we don't have a need for any internal state, class construction, any routing logic, any i18next middlewares, etc. The _app HOC should take the config, like so:

User's custom _app:

import { appWithTranslation } from 'next-i18next/dist/commonjs/hocs/app-with-translation'
import path from 'path'

const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />

export default appWithTranslation(MyApp, {
  defaultLanguage: 'en',
  otherLanguages: ['de'],
  localePath: path.resolve('./public/static/locales')
})

Individual pages should continue passing namespacesRequired up to the HOC via props, but can do so via either getStaticProps or getServerSideProps – either way works agnostically to next-i18next, as it just comes up as WrappedComponent.props:

User's homepage:

const Homepage = ({ t }) => (
  <Link href='/second-page'>
    <button
      type='button'
    >
      {t('to-second-page')}
    </button>
  </Link>
)

export const getStaticProps = async () => ({
  props: {
    namespacesRequired: ['common', 'footer'],
  }
})

export default withTranslation('common')(Homepage)

The app-with-translation HOC itself can now just consume the locale via router.locale.

app-with-translation HOC:

export const appWithTranslation = (WrappedComponent, userConfig) => {

  const WrappedComponentWithSSR = withSSR()(WrappedComponent)
  const config = createConfig(userConfig)
  const { initPromise, i18n } = createClient(config)

  const AppWithTranslation = (props) => {
    const router = useRouter()
    i18n.changeLanguage(router.locale)
    return (
      <I18nextProvider
        i18n={i18n}
      >
        <WrappedComponentWithSSR
          {...props}
        />
      </I18nextProvider>
    )
  }

  return hoistNonReactStatics(
    AppWithTranslation, WrappedComponent,
  )
}

Things I haven't figured out yet:

  1. How to await the i18next init promise. Do we need to wrap getStaticProps/getServerSideProps, to be able to await the promise? Probably so...
  2. How to properly configure i18next in the absence of i18next-http-middleware. Just from an initial look, it seems like that package was doing a lot of work for us for free, in terms of configuration

Call for Contributors

That's it, really. I don't think it should be more than a few days of work to strip next-i18next down to a much slimmer, simpler package that is agnostic of routing.

I would greatly appreciate the help of anyone who wants to work on this. If anyone wants to dedicate a good chunk of time, let me know and I will consider adding people as contributors, so that we can collaborate on a next-v10 branch, or something similar.

I'd also greatly appreciate advice and feedback from @adrai and @jamuhl, as it looks like the future of next-i18next is basically just a thin i18next wrapper on top of NextJs.

SalahAdDin commented 3 years ago

Why a HOC? Wouldn't it better to use hooks instead?

underfisk commented 3 years ago

@SalahAdDin What about react legacy apps? If it goes only for hooks, they lose support because they have no functional approach as such

kachkaev commented 3 years ago

Apps with React 16.8.0+ support hooks, which means that any project using React 16.0.0+ (Sep 2017) can upgrade without any breaking changes.

React 16.8.0 is a minimum requirement for next-i18next already:

https://github.com/isaachinman/next-i18next/blob/abdf06545410f340b0529e3448f8b102ab840249/package.json#L115-L118

underfisk commented 3 years ago

@kachkaev Okay then it would make sense

jansepke commented 3 years ago

@kachkaev but still many have not migrated class components to functional components. next-i18next currently supports useTranslation and withTranslation already so you can choose between HOC or hook usage (but maybe its not documented).

If you mean the appWithTranslation HOC: afaik hooks cannot insert a provider block around your app

underfisk commented 3 years ago

@jansepke but useTranslation does not work, i have tried and it doesn't, so you are required to receive the TFunction. Its weird because i18 should have the loaded translations but if you try to useTranslation, the translations are empty (Right now with my hooks app i'm forced to use the T everywhere with HoC because next-i18next does not inject correctly)

mercteil commented 3 years ago

@jansepke hooks are not for component wrapping. But it is still just a function. You could use any function to be a factory. However we would not use hooks in the react sense anymore. Thus we use Higher Order Components(=Factory Functions). However if you really need to you can return a Component from a hook and thus wrap it within a hook. For next.js => SSR does not render hooks. They are executed on the client side (see useEffect eg).

@underfisk I use useTranslation application wide. Your configuration seems to be falsy. Anyhow, better to open an issue and not communicate it under this topic.

itxtoledo commented 3 years ago

@jansepke mas useTranslation não funciona, eu tentei e não funciona, então você deve receber a função TFunction. É estranho porque o i18 deve ter as traduções carregadas, mas se você tentar usar a tradução, as traduções estão vazias (agora, com meu aplicativo de ganchos, sou forçado a usar o T em todos os lugares com HoC porque next-i18next não injeta corretamente)

image

I use this hook and works perfectly

underfisk commented 3 years ago

@mercteil Not really, i did add the HoC, context and the props but i'm using SSG, that might be the problem, not using SSR here

isaachinman commented 3 years ago

We'll be using a HOC, let's keep discussion in this issue on topic.

underfisk commented 3 years ago

@isaachinman withTranslation hook only works if we use the custom of Next18 right? I though using i18n useTranslation would read the under lying context. Also in documentation its missing how you can skip translations in a specific page, right now i get warnings all the time even in pages i don't want locales, is there are way we can surpress locales at specific pages?

kachkaev commented 3 years ago

How to await the i18next init promise

What about throwing a promise in one of the HOCs and thus leveraging the Suspense mechanism? 🤔

isaachinman commented 3 years ago

@kachkaev That's an experimental feature, and not really relevant what we want to do. The moments where we will need to await the init promise are either during SSG or SSR. A fully initialised i18next instance is serialised and sent down to the client per req, so there is no need to await in a client side setting.

MathiasKandelborg commented 3 years ago

This looks promising! :tada: Let's get some things going! :partying_face:

The project's new scope is "simply" integrating i18next with Next.js, right?

Would it be fine for a first PR to try and cut as much as possible, get all the way down to the bone, and then get started with Next.js v10 and i18next?

I think a project or a milestone (on this Github repo) with some issues etc. would help organize things a bit better. It might just be me liking to organize things a bit too much, though.


There are several approaches to be made in regards to configuration and file loading. I'll spend some time today looking at different ways to approach the integration.

I found an approach - i.e., the files I've linked to before - to include the translated files on build/compile time for SSG pages using a Next.js lifecycle (haven't tested others, but the concept should be the same). This approach loads the relevant namespace files before any requests are made, so the data is included in the initial request. If it's possible to use the aforementioned method with i18next and all the different rendering approaches, that will probably be superior to any other method I can think of. I'm not sure about it, though. I'm just putting some thoughts out there.


BTW, sorry for the late response!

I've had a hectic week so far; I'm going to spend at least a few hours a day trying to solve this. I can't really progress my own project as I want to unless I use a library like this, so it's getting personal now! :grin:

isaachinman commented 3 years ago

Hey @MathiasKandelborg.

Would it be fine for a first PR to try and cut as much as possible, get all the way down to the bone, and then get started with Next.js v10 and i18next?

Not sure that's necessary – if you want to focus on something, let's begin with the core functionality. I have the context necessary to quickly clean up the repo once the core work is done.

I found an approach - i.e., the files I've linked to before - to include the translated files on build/compile time for SSG pages using a Next.js lifecycle

Can you explain what you mean? Loading translation files is something that next-i18next has already solved, and I am not anticipating any changes will be needed in regards to that.

Also, I will have at least a few hours to look into this tomorrow (8 Nov), so let me know if you make any progress in the meantime.

isaachinman commented 3 years ago

Pretty crucial problem: _app does not support getStaticProps or getServerSideProps. I'm unsure how to write a HOC that adds an i18n instance, as we need to init it during SSR/SSG, and then serialise it via pageProps down to the client. Using getInitialProps seems like a step backward, as these are static data requirements.

Attempting to override, or compose upon getStaticProps or getServerSideProps manually would be tricky or impossible, as I'm not sure we can access pathname.

Would be grateful for any suggestions.

kachkaev commented 3 years ago

What if getXyzProps returns the actual translations and not paths / namespaces? Roughly speaking,

export const getServerSideProps = async () => { // or getStaticProps
  return {
    props: {
      translationLookup: loadI18nextTranslations(["myPage", "whatever"]), // never executed on the client
    },
  };
};

const MyPage = ({ translationLookup }) => {
  return (
    <NextI18nextProvider translationLookup={translationLookup}>
      pageContents
    </NextI18nextProvider>
  );
};

export default MyPage;

The problem with this though is that if every page has loadI18nextTranslations(..., "common"), then common translations would be loaded on every transition to another page. Perhaps, they could be some special trick for those via _document.tsx? What I mean is preloading common namespaces there and thus not having to include them into the pages.

kachkaev commented 3 years ago

An alternative solution would involve an API endpoint and Suspence inside some sort of <NextI18nextProvider namespaces=["foo", "bar"]/>. If the instance of i18next inside the NextI18nextProvider does not have the expected locales loaded, it throws a promise, which resolves after the API request has been fulfilled. What I mean here is calling something like /api/next-i18next-translations?locale=ab-CD&namespaces=commons,myPage.

In addition, each use of a not-yet-loaded namespace via t / Trans can also trigger Suspense, but show a warning. The warning will help the devs predefine the locales they expect to be used at the top of each page and thus make the app faster. Still, because of how Suspense works, the app will be functional even if a requested namespace is not yet loaded in the middle of a page.

jan-wilhelm commented 3 years ago

So am I understanding it correctly that this will not allow SSG for dynamic pages without getStaticPaths, because it forces you to export a getStaticProps function?

What about a scenario where I have a dynamic route with unknown paths, such as products/[productId]? I would want this page to be

jvcmanke commented 3 years ago

@jan-wilhelm

So am I understanding it correctly that this will not allow SSG for dynamic pages without getStaticPaths, because it forces you to export a getStaticProps function?

If the approach taken is the one suggested by @kachkaev here (return the translations as props of getXyzProps) I think it will work the same for getStaticPaths as you also have to return the props for every dynamic page you want to generate.

I am not using next-i18next in my project and have been using this approach, and it works well, it is not as clean as a HOC but it works.

I currently have a server i18n instance that has all namespaces and languages loaded (maybe not too efficient) that is used to get the needed translations for each page in getStaticProps for example, and a fresh i18n instance created in _app that gets loaded with the translations from props during build (os server rendering if using getServerSideProps) and provided to the react-i18next provider.

This second one can be made into a HOC for easy use with any custom _app as it only needs to grab translations from props, wrap the whole thing with the provider and pass the context along for the actual App. Could even be made into a hook for those that want more control over where the provider lies within the structure.

alradadi commented 3 years ago

Pretty crucial problem: _app does not support getStaticProps or getServerSideProps. I'm unsure how to write a HOC that adds an i18n instance, as we need to init it during SSR/SSG, and then serialise it via pageProps down to the client. Using getInitialProps seems like a step backward, as these are static data requirements.

Attempting to override, or compose upon getStaticProps or getServerSideProps manually would be tricky or impossible, as I'm not sure we can access pathname.

Would be grateful for any suggestions.

It's possible to pass additional props to the page in _document.js by customizing the renderPage function.

https://nextjs.org/docs/advanced-features/custom-document#customizing-renderpage

Could we pass the instance that way ?

maxmalov commented 3 years ago

What if getXyzProps returns the actual translations and not paths / namespaces?

Btw, that's the option next-translate provides for loading translations https://github.com/vinissimus/next-translate/blob/master/examples/without-build-step/pages/index.js#L27

Perhaps, they could be some special trick for those via _document.tsx?

AFAIK _document.tsx is actual only for rendering the whole page at the server-side, it won't work during page transitions within the application.

Pretty crucial problem: _app does not support getStaticProps or getServerSideProps. I'm unsure how to write a HOC that adds an i18n instance, as we need to init it during SSR/SSG, and then serialise it via pageProps down to the client. Using getInitialProps seems like a step backward, as these are static data requirements.

Another problem is that getInitialProps can be used to serialize i18n client, but getServerSideProps cannot, due to stricter serialization logic.

After playing w/ combination of next-i18next and next-translate approaches, I've collected some thoughts that might be useful:

isaachinman commented 3 years ago

Quick update: I have spent a few hours working on this today, and have a solution nearly ready. I'll post an update here shortly with a demo branch.

isaachinman commented 3 years ago

OK – the core idea can be found on the next-v10 branch.

I was able to delete the majority of next-i18next's source code, and the core functionality now just depends on appWithTranslation, and a serverSideTranslations function.

Here's the idea:

User's next-i18next.config.js file (at project root)

const path = require('path')

module.exports = {
  defaultLanguage: 'en',
  otherLanguages: ['de'],
  localePath: path.resolve('./public/static/locales')
}

User's pages/_app:

import { appWithTranslation } from 'next-i18next'

const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />

export default appWithTranslation(MyApp)

User's pages/index:

import { withTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

import Header from '../components/Header'

const Homepage = ({ t }) => (
  <>
    <Header title={t('h1')} />
  </>
)

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...await serverSideTranslations(locale, ['common', 'footer']),
  }
})

export default withTranslation('common')(Homepage)

And that's it! For those curious about internal implementation, the two important files to check are:

  1. appWithTranslation
  2. serverSideTranslations

I believe that this serverSideTranslations approach will work with any of the NextJs data fetching methods, and my brief bit of local testing seems to indicate that. I believe that in doing this, we can actually side-step the issue of performing client-side translation loading, as those data dependencies will already be compiled to JSON via SSG, or will be computed server-side via SSR.

I would love if people can check out the next-v10 branch, and try to find any potential issues with this approach.

Once we come to a general consensus on this approach, next steps will be:

  1. Further cleanup of the repo, upgrade TypeScript version, etc
  2. Rewrite test coverage
  3. Start releasing a beta at next-i18next@nextv10-beta
martpie commented 3 years ago

Any reason to have a i18next.config.js when most of these variables are already available on next.config.js?

isaachinman commented 3 years ago

@martpie I had considered that, but in the end, two things stopped me:

  1. There is still a discrepancy between the config shape for NextJs and for i18next. This is kind of annoying, and I have also considered writing a nextConfigToI18nextConfig helper. But, for all uses cases outside of the most basic, people are going to unfortunately need to maintain two config sets regardless
  2. I'd like to avoid adding new/unsupported stuff to next.config.js, because I'm not sure the Vercel team would be happy with that, and they sanitise/validate the object, if I recall correctly

Edit: On second thought, it might make sense to write a helper func that goes the other way, ie i18NextConfigToNextJsConfig. Then people can at least import their next-i18next.config.js file and not have config duplication. I do believe NextJs's i18n config options are a subset of next-i18next's.

TheFullResolution commented 3 years ago

@isaachinman I think sperate config files were a good call 👍 In the case of the project I am currently working on, inside next.config.js we use locales together with country code (nl-NL, nl-BE, fr-FR, fr-BE etc.), this allows us to serve different offer base on the country selected and display content in the right language. However, we are maintaining only one translation for Frech (fr.json) or Dutch (nl.json)

isaachinman commented 3 years ago

@TheFullResolution That's a separate point, and one I hadn't considered yet: how will locale fallbacks work, with this new NextJs v10 setup?

You can see that I am naively setting the language via router.locale here. But probably, we will need to pass router.locale into some i18next detector, to get the correct fallback sequence. At the moment, it looks like if I pass locales: ['en-US', 'de-DE'] into NextJs, and locales: ['en', 'de'] into i18next, the implementation breaks.

If @adrai or @jamuhl could give some advice on that, I'd appreciate it! Not sure if that kind of fallback behaviour is already built in. It appears that there is no fallback sequence when setting lng: initialLocale on i18next client init.

jamuhl commented 3 years ago

@isaachinman fallback works independently of using a language detector or directly setting the lng on i18next.init

per default the fallback will be lng-Country -> lng -> options.fallback

setting load options to and assuming having a lng-Country passed in options or via detection:

'languageOnly': lng -> options.fallback 'currentOnly': lng-Country -> options.fallback

So as i18next does not have an options locales: ['en', 'de'] my guess this relates to preload options? idk?


Edit: load options resolution

isaachinman commented 3 years ago

@jamuhl Well, the fallback doesn't seem to be working in this case, for some reason. The i18next.options object can be seen here. You can see that we have:

"supportedLngs": [
    "de",
    "en",
    "cimode"
],

But also:

"lng": "en-US",

Any ideas?

jamuhl commented 3 years ago

guess it's because en-US is not in the list of supported languages: setting "nonExplicitSupportedLngs": true might work

isaachinman commented 3 years ago

@jamuhl Adding both nonExplicitSupportedLngs and nonExplicitWhitelist as true to the config didn't fix the problem.

jamuhl commented 3 years ago

you got the https://gist.github.com/isaachinman/62b3642b591367b8951ab159a77153f8#file-next-v10-i18next-locale-fallback-L24 load option set to currentOnly which leads to:

'currentOnly': lng-Country -> options.fallback

isaachinman commented 3 years ago

@jamuhl This config isn't working for en-US either:

"defaultNS": "common",
"fallbackLng": false,
"fallbackNS": false,
"whitelist": [
    "de",
    "en",
    "cimode"
],
"nonExplicitWhitelist": true,
"supportedLngs": [
    "de",
    "en",
    "cimode"
],
"nonExplicitSupportedLngs": true,
"load": "all",
"preload": [
    "de",
    "en"
],
jamuhl commented 3 years ago

@isaachinman what is not working? I assume with above config you get i18next.languages = ['en-US', 'en'] which will on serverside miss en-US resources as those were not preloaded

isaachinman commented 3 years ago

@jamuhl I was expecting to be able to set up supportedLngs: ["en", "de"], and then init an i18next client with lng: "en-US", and have it automatically change the locale to en, as that is our "closest" supported language. Are you saying that is not supported?

jamuhl commented 3 years ago

it will not change the language....but resolve translations from en-US and en plus fallback.json (in that order)

https://www.i18next.com/principles/fallback#language-fallback

if you like to change en-US to only load en you will need to set load: 'languageOnly'

SalahAdDin commented 3 years ago

I'm not sure if this comment should be here but every issue i found about SSG redirects me to here, so, i add here:

since we cant use serverSideProps together with initialProps. we can make use of React FunctionComponent.defaultProps here is the workaround that works for me, and it still works with initialProps (#615 (comment))

  1. particular page
Page.defaultProps = {
      i18nNamespaces: ['home-page']
}
  1. main app / _app.tsx
MyApp.getInitialProps = async (appContext: AppContextType<Router>) => {
    const appProps = await App.getInitialProps(appContext)
    const defaultProps = appContext.Component.defaultProps
    return {
        ...appProps,
        pageProps: {
            namespacesRequired: [...(appProps.pageProps.namespacesRequired || []), ...(defaultProps?.i18nNamespaces || [])]
        }
    }
}

Comment here.

Well, i thought it would work good, but it works with delay: Screen Capture_select-area_20201201232330

At the first loading, it does not load the skill-test namespaces, i need to reload it to get that namespace active and the proper localization strings.

Inspecting it for the first time we get this: Screen Capture_select-area_20201201232220

After reloading, we can find the required namespace: image

Both namespaces are in the option, but just the first one, defined on _app.js by getInitialProps, is loaded.

Why does this happen?

TheFullResolution commented 3 years ago

@TheFullResolution That's a separate point, and one I hadn't considered yet: how will locale fallbacks work, with this new NextJs v10 setup?

You can see that I am naively setting the language via router.locale here. But probably, we will need to pass router.locale into some i18next detector, to get the correct fallback sequence. At the moment, it looks like if I pass locales: ['en-US', 'de-DE'] into NextJs, and locales: ['en', 'de'] into i18next, the implementation breaks.

If @adrai or @jamuhl could give some advice on that, I'd appreciate it! Not sure if that kind of fallback behaviour is already built in. It appears that there is no fallback sequence when setting lng: initialLocale on i18next client init.

Sorry, for the late reply - didn't see the question. So to give you some more information, the project is in a very early stage, so for now we have a very basic setup.

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

const resources = {
    en: {
        common: require('../../public/locales/en/common.json'),
        home: require('../../public/locales/en/home.json'),
    },
    fr: {
        common: require('../../public/locales/fr/common.json'),
        home: require('../../public/locales/fr/home.json'),
    },
    nl: {
        common: require('../../public/locales/nl/common.json'),
        home: require('../../public/locales/nl/home.json'),
    },
} as any;

type Config = string | boolean | null | string[];

export const i18nConfig: Record<string, Record<string, Config> | Config> = {
    resources,
    ns: ['common', 'home'],
    defaultNS: 'common',
    interpolation: {
        escapeValue: false,
    },
    lng: 'en',
};

if (!i18n.isInitialized) {
    i18n.use(initReactI18next).init({
        ...i18nConfig,
    });
}

export { i18n };

And we load the language like this:

function MyApp({ Component, pageProps }: AppProps) {
    const { locale } = useRouter();

    useEffect(() => {
        i18n.changeLanguage(locale!);
    }, [locale]);

    return (
        <I18nextProvider i18n={i18n}>
            <Component {...pageProps} />
        </I18nextProvider>
    );
}

export default MyApp;

Obviously, this is a very naive implementation, however, it allows us to get UI components out of the door.

Ourlocales inside next.config.js is en-NL, nl-NL, nl-BE, fr-BE when we load the page with locale nl-NL and pass this to i18n.changeLanguage(locale!); it just works and loads Dutch translations.

dcporter44 commented 3 years ago

Just for thought: i18next's fallback functionality is going to be tricky to use with Next 10's new routing.

Next has its own locales and defaultLocale options and they're both required when using i18n in Next. It has 2 ways of determining the language: through the first item in the URL path (example.com/en) or through the Accept-Language header. It uses its own logic to pass the language into its router object. If "en-uk" is passed from the client, but your app only supports "en", then Next will pass the defaultLocale to the router. You have to somehow parse it out yourself through the request header or parse the URL (depending on which method Next is using). Then after you've parsed it, run it through i18next to determine the correct fallback.

I'm starting to go through the docs to see if there is a way to somehow intercept the request prior to routing. It seems the goal is to intercept, extract the language, run through i18next to detect the fallback, and set that as Next's language path. I'll continue researching.

isaachinman commented 3 years ago

Thanks @dcporter44 – if you can look into it, that'd be much appreciated. Syncing locale/fallback between NextJs and i18next is what we're currently stuck on.