Next-i18next: Next.js 10

Created on 27 Oct 2020  ·  153Comments  ·  Source: isaachinman/next-i18next

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

Most helpful comment

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.

All 153 comments

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!

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

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.

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.

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.

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.

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?

@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.

@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.

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

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

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

@kachkaev Okay then it would make sense

@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

@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)

@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.

@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

@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

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

@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?

How to await the i18next init promise

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

@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.

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:

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.

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.

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.

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.

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

  • translated
  • not using getStaticProps because I can't also export getStaticPaths (unknown paths) and NextJS requires that
  • pre-rendered on build time, because it is always the same loading skeleton, no matter the productID

@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.

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 ?

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:

  • using i18next http middleware in server-side methods simplifies things a lot,
  • serialize initialI18nStore and initialLanguage (https://react.i18next.com/latest/ssr#passing-initial-translations-initial-language-down-to-client) in server-side methods,
  • create i18n client from scratch using serialized data in custom _app.js at the server-side, and use singleton at the client-side (but be aware of https://github.com/i18next/react-i18next/blob/master/src/useSSR.js#L11),
  • there is an option to use a high-order function to reduce the boilerplate of loading translations in server-side methods, e.g.

    export const getServerSideProps = withTransations(["myPage", "whatever"])(async ctx => { ... });

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.

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

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

@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.

@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)

@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.

@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

@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?

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

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

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

@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"
],

@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

@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?

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'

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 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.

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.

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.

So it really doesn't look like there's an out-of-the-box way to customly handle what language Next i18n uses. It simply parses the Accept-Language and moves on.

There is an open PR to more appropriately handle regional fallbacks. For example, automatic fallback from en-GB to en.
See here: https://github.com/vercel/next.js/issues/18676 This issue has been added to a Vercel milestone set to be complete early January.

Obviously, this PR doesn't solve the issue of being able to handle language fallback ourselves. But it does fall more in line with i18next's default fallback functionality.

Next delegates the locale route at the highest level - prior to even reaching the _app.js component. At the server level, you could theoretically transform the incoming request header for Accept-Language before the request hits Next, but obviously this isn't a strategy that can be used with static apps.

I have an very basic integration of i18next and next.js 10 on a project in production (https://game-park.com/ if you want to have a look).

  // in _app.tsx
  const {locale} = useRouter()
  const i18n = useMemo(() => createI18nInstance(locale), [])
  useEffect(() => {
    if (locale) {
      i18n.changeLanguage(locale).catch(error => console.error('Error while changing i18n language', error))
    }
  }, [locale])
  return (
    <I18nextProvider i18n={i18n}>
    [...]
    </I18nextProvider>
  )

with createI18nInstance.tsx:

import i18next from 'i18next'
import {initReactI18next} from 'react-i18next'
import translations from './translations.json'

export default function createI18nInstance(language = 'en') {
  const i18n = i18next.createInstance({
    lng: language,
    fallbackLng: 'en',
    resources: translations
  })
  i18n.use(initReactI18next).init().catch(error => console.error('Error during i18next instance creation', error))
  return i18n
}

By using i18next.createInstance, I am able to generate static pages at build time for each language. Of course this configuration is quite simple since I do not have XHR backend, but it might be useful to some of you.

I also have used react-i18next/i18next for i18n. Next.js v10 with i18n routing doesn't support SSG so I've built a starter that doesn't use the i18n routing capabilities.

https://github.com/linaro-marketing/serverless-nextjs-i18n-ssg-ssr-starter

With this starter I can deploy to my own infrastructure using Serverless-nextjs, statically optimize pages, and also have the ability to use the SSR functionality.

I really do hope that Next.js v10 gets SSG support added soon!

I wanted to see what it would be like to just use react-i18next with Next 10's i18n functionality. This is about as minimal of a working example I could create that also simulates next-i18next's namespace loading. It also allows use of the useTranslation hook rather than using providers or HOC.

next.config.js

module.exports = {
  i18n: {
    locales: ['en', 'de', 'es'],
    defaultLocale: 'en'
  }
};

i18n.js

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

export const i18nextInit = (router, namespaces = ['default']) => {
  if (!i18next.isInitialized) {
    i18next.use(initReactI18next).init({
      lng: router.locale,

      supportedLngs: router.locales,

      fallbackLng: router.defaultLocale,

      react: {
        useSuspense: false
      },

      resources: {}
    });
  }

  if (i18next.language !== router.locale) {
    i18next.changeLanguage(router.locale);
  }

  namespaces.forEach(ns => {
    if (!i18next.hasResourceBundle(router.locale, ns)) {
      const data = require(`./content/locales/${router.locale}/${ns}.json`);
      i18next.addResourceBundle(router.locale, ns, data);
    }
  });

  i18next.setDefaultNamespace(namespaces[0]);
};

_app.js

import '../styles/globals.css';
import { i18nextInit } from '../i18n.js';
import { useRouter } from 'next/router';

export default function MyApp({ Component, pageProps }) {
  const router = useRouter();

  i18nextInit(router, pageProps.i18nNamespaces);

  return <Component {...pageProps} />;
}

index.js

import { useTranslation } from 'react-i18next';

export default function IndexPage(props) {
  const { t } = useTranslation();

  return (
    <div>
      <p>{t('welcome_msg')}</p>
      <p>{t('secondary:hello_world')}</p>
    </div>
  );
}

export async function getStaticProps() {
  return {
    props: {
      i18nNamespaces: ['default', 'secondary']
    }
  };
}

@dcporter44 I used your example and it works. I did some tweaks to support the project I am working on (we use locale like en-NL, nl-NL, but load a single translation file per language).

I hardcode the languages and extract lang code from locale string:

export const i18nextInit = (router: NextRouter, namespaces = ['common']) => {
    const lng = getLangFromLocale(router.locale!);

    if (!i18next.isInitialized) {
        i18next.use(initReactI18next).init({
            lng,
            supportedLngs: ['nl', 'en', 'be'],
            fallbackLng: 'en',
            react: {
                useSuspense: false,
            },

            resources: {},
        });
    }

    if (i18next.language !== lng) {
        i18next.changeLanguage(lng);
    }

    namespaces.forEach((ns) => {
        if (!i18next.hasResourceBundle(lng, ns)) {
            const data = require(`../../public/locales/${lng}/${ns}.json`);
            i18next.addResourceBundle(lng, ns, data);
        }
    });

    i18next.setDefaultNamespace(namespaces[0]);
};

here is a naive extract function:

export const getLangFromLocale = (currentLocale: string) => {
    return currentLocale.split(/[-_]/)[0];
};

@TheFullResolution Yeah that should work. But it's important to note that Next will only pass through a locale to the router if the locale is in the locales config option in next.config.js. So if your goal is to fallback _all_ "nl-*" locales to "nl", this implementation won't work. You would theoretically have to pass every possible "nl-" locale to the locales config option in order for it to be passed through to your i18nextInit function (or else Next will pass through the defaultLocale).

Even if you did add all "nl-*" locales to the locales config option, it's important to note that the URL would not redirect to "/nl/page". For example, if Next detects "nl-NL" from the Accept-Language header and you added "nl-NL" to your locales config array, the url would appear as "/nl-NL/page". Next will NOT redirect to "/nl/page".

@dcporter44 Yes, I am aware of it. And in the case of this project, it is even desirable, as there is a different offer showed base on the country, so we do need to have nl-NL or nl-BE, while loading just one language JSON (nl)

@dcporter44 interesting! What about production builds and client-side routing to a page with additional namespaces? I'm curious how this part of your solution can handle this:

    namespaces.forEach((ns) => {
        if (!i18next.hasResourceBundle(lng, ns)) {
            const data = require(`../../public/locales/${lng}/${ns}.json`); // 👈 👀
            i18next.addResourceBundle(lng, ns, data);
        }
    });

I have a working implementation with Prismic CMS based on @dcporter44's example here in case that is useful to anyone!

@kachkaev @mikeyrayvon Ah, so the implementation I wrote above doesn't seem to allow Next to use its Automatic Static Optimization. This is most likely because we are dynamically importing server-side files. In my case, my locale files were in a folder called content/locales. So Next is only using SSR and not SSG in my implementation.

This behavior seems to be undocumented. The docs say that SSG will only be prevented if using getServerSideProps or getInitialProps... which I'm not even using. Regardless, I guess I can understand why dynamically loaded files prevent SSG.

I've just implemented a better solution by loading the translations in getStaticProps instead of loading translations dynamically in _app.js like I was before. Now when I run npm run build, the pages are now built as .html files. This means that they are now successfully being optimized as static pages. My build folder now actually has generated static HTML files for each language for each page which is really cool.

Anyway, here is what I've come up with instead:

next.config.js

module.exports = {
  i18n: {
    locales: ['en', 'de', 'es'],
    defaultLocale: 'en'
  }
};

i18n.js

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

export const i18nextInit = async (router, i18nResources) => {
  if (!i18nResources) {
    return;
  }

  const { translations, namespaces } = i18nResources;

  if (!i18next.isInitialized) {
    i18next.use(initReactI18next).init({
      lng: router.locale,

      supportedLngs: router.locales,

      fallbackLng: router.defaultLocale,

      ns: namespaces,

      react: {
        useSuspense: false
      }
    });
  }

  i18next.setDefaultNamespace(namespaces[0]);

  if (i18next.language !== router.locale) {
    i18next.changeLanguage(router.locale);
  }

  namespaces.forEach(ns => {
    if (!i18next.hasResourceBundle(router.locale, ns)) {
      i18next.addResourceBundle(router.locale, ns, translations[ns]);
    }
  });
};

export async function getTranslations(locale, namespaces) {
  const translations = {};

  for (let i = 0; i < namespaces.length; i++) {
    const ns = namespaces[i];

    const data = require(`./content/locales/${locale}/${ns}.json`);

    translations[ns] = data;
  }

  return { translations, namespaces };
}

_app.js

import '../styles/globals.css';
import { i18nextInit } from '../i18n.js';
import { useRouter } from 'next/router';

export default function MyApp(props) {
  const { Component, pageProps } = props;

  const router = useRouter();

  i18nextInit(router, pageProps.i18nResources);

  return <Component {...pageProps} />;
}

index.js

import { useTranslation } from 'react-i18next';
import { getTranslations } from '../i18n';

export default function IndexPage(props) {
  const { t } = useTranslation();

  return (
    <div>
      <p>{t('welcome_msg')}</p>
      <p>{t('secondary:hello_world')}</p>
    </div>
  );
}

export async function getStaticProps({ locale }) {
  return {
    props: {
      i18nResources: await getTranslations(locale, ['default', 'secondary'])
    }
  };
}

@dcporter44 thanks for sharing the code, I'm using here and the translation is working well, but when I click on the Nextjs Link component to change the language I get a console warning (the translation change works normally though):

Warning: Cannot update a component (`Home`) while rendering a different component (`MyApp`). To locate the bad setState() call inside `MyApp`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
    at MyApp (webpack-internal:///./src/pages/_app.jsx:32:24)

My _app.jsx:

function MyApp({ Component, pageProps }) {
  const contextValue = useGlobalContextValue();

  const router = useRouter();

  i18nextInit(router, pageProps.i18nResources);

  return (
    <GlobalContext.Provider value={contextValue}>
      <Component {...pageProps} />
    </GlobalContext.Provider>
  );
}

How I'm using the Link component:

<li key={locale}>
  <Link href="/" locale={locale}>
    <a className={router.locale === locale ? styles.selected : ""}>
      {locale}
    </a>
  </Link>
</li>

It doesn't happen with normal Link component (to navigate to another page, for example), it's only when changing the language. Any idea why? Thanks!!

@alexandrepaivaa Thanks for bringing this up. I'm actually seeing this too. Everything seems to still work as expected regardless of the warning. I haven't had time to dig into this yet. I'll look into it and report back here.

@alexandrepaivaa So I did a little research and found this issue (#1124) on the react-i18next repo. Some users in the thread seem to be getting the same warning when using the useTranslation hook. There's not really any clear solution in the issue. But it seems to be something to do with the changeLanguage function triggering a re-render within useTranslation.

In the meantime, I was able to get rid of the warning by removing useTranslation from my code, and instead just using i18next.t. I don't believe there's any real benefit to using the useTranslation hook in our use case. I could be wrong. But i18next.t seems to work fine. Maybe someone else in this thread will have better insight into any potential shortcomings with this implementation, but it seems to work just fine and no warnings:

index.js:

import i18next from 'i18next';
import { getTranslations } from '../i18n';

export default function IndexPage(props) {
  return (
    <div>
      <p>{i18next.t('welcome_msg')}</p>
      <p>{i18next.t('secondary:hello_world')}</p>
    </div>
  );
}

export async function getStaticProps({ locale }) {
  return {
    props: {
      i18nResources: await getTranslations(locale, ['default', 'secondary'])
    }
  };
}

But I guess this actually begs a bigger question of "how badly do we even need react-i18next"? Could we not just use the i18next library by itself? I understand that react-i18next gives us access to React-specific components, hooks, and HOC's, but if the above implementation is all you need, then maybe i18next by itself is enough. Depends on your use case I guess. But I'd assume that in 90% of use-cases with Next 10, the above example with just i18next will work fine.

@alexandrepaivaa I'm hitting the same issue:

Cannot update a component (`Home`) while rendering a different component (`CustomApp`)

So far I've ended up splitting the initial changeLanguage from the dynamic change via useEffect.

The lines that I've wrapped are:

if (i18next.language !== router.locale) {
  i18next.changeLanguage(router.locale);
}

On init the i18next.language is undefined and that's okay, but when we change the url we hit the problem. I've been able to extract the initialization in a dedicated hook. You might check out the gist at:

https://gist.github.com/kachar/ee23a13dffa493179e835f3482f3b470#file-usenextlocale-ts


But then I hit a difference between the server and the client in the initial language. Needs more tweaks

Warning: Text content did not match. Server: "Начало" Client: "Home"

I'm new to i18n and this is my current solution, any opinion?
It is working pretty fine for me, no warning/error in console, and next can generate static HTML for each page.

_app.tsx

import React, { useEffect, useState } from "react"
import i18next, { ResourceLanguage } from "i18next"
import { AppProps } from "next/app"
import { useRouter } from "next/router"
import { I18nextProvider } from "react-i18next"

export default function MyApp(props: AppProps): JSX.Element {
  const { Component, pageProps } = props
  const router = useRouter()
  const [instance] = useState(i18next.createInstance())
  const locale = router.locale ?? "en"
  const namespaces = ["default"].concat(pageProps?.i18nNamespaces ?? [])

  if (!instance.isInitialized) {
    instance.init({
      supportedLngs: router.locales,
      fallbackLng: router.defaultLocale,
      lng: locale,
      ns: namespaces,
      defaultNS: "default",
      react: {
        useSuspense: false,
      },
      interpolation: {
        escapeValue: false,
      },
      resources: {
        [locale]: namespaces.reduce<ResourceLanguage>(
          // eslint-disable-next-line
        (pre, ns) => ({ ...pre, [ns]: require(`../locales/${locale}/${ns}.json`) }),
          {},
        ),
      },
    })
  }

  namespaces.forEach((ns) => {
    if (!instance.hasResourceBundle(locale, ns)) {
      // eslint-disable-next-line
        instance.addResourceBundle(locale, ns, require(`../locales/${locale}/${ns}.json`))
    }
  })

  useEffect(() => {
    if (instance.language !== locale) {
      instance.changeLanguage(locale)
    }
  }, [instance, locale])

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

page/index.tsx

import { GetStaticProps } from "next"
import { useTranslation } from "react-i18next"

export default function IndexPage() {
  const { t } = useTranslation()

  return (
    <div>
      <p>{t("welcome_msg")}</p>
      <p>{t("secondary:hello_world")}</p>
    </div>
  )
}

export const getStaticProps: GetStaticProps = async () => ({
  props: {
    i18nNamespaces: ["secondary"],
  },
})

@Summon528 I've tried out your code above. I think the reason you're able to avoid the warnings is your use of useEffect with changeLanguage (which also sounds like what @kachar said he's doing). But I notice a slight delay when changing language. When I switch to a new page with a different language, the previous language's translations show for a brief moment before the new language's translations come in. If you put a console log in your _app's useEffect and a console log on the next page, you'll see that useEffect runs _after_ rendering the page. So that explains the slight delay in language change.

I'm not too sure what your use of I18nextProvider is doing. I believe the point of the provider is to be used with the withTranslation consumer so you can access that specific i18n instance. I don't think the useTranslation hook is meant to be used along with the I18nextProvider. Your code would likely work the same without the provider. Maybe I'm wrong. Is there a reason you're using the provider here?

@alexandrepaivaa @kachar @Summon528 So I believe I found a solution that allows us to use the useTranslation hook without getting the warning that @alexandrepaivaa mentioned here.

The issue is the re-render triggered within useTranslation when changeLanguage is called. One solution is to call changeLanguage in a useEffect like @kachar and @Summon528 tried. However, this causes changeLanguage to be called _after_ the page is rendered as I mentioned in the comment above. So not an ideal solution.

Another solution is to just not use useTranslation and instead use i18next.t like I showed in this comment. But this doesn't allow us to use the useTranslation hook. I'm not sure if that's really even a problem...

But if you really want to use useTranslation, I discovered you could do it this way without warnings:

_app.js

import '../styles/globals.css';
import { i18nextInit } from '../i18n.js';
import { useRouter } from 'next/router';
import { useTranslation } from 'react-i18next';

export default function MyApp(props) {
  const { Component, pageProps } = props;

  const router = useRouter();

  i18nextInit(router, pageProps.i18nResources);

  const { t } = useTranslation();

  return <Component {...pageProps} t={t} />;
}

And then simply use the t function passed into your page via props:

index.js

import { getTranslations } from '../i18n';

export default function IndexPage({ t }) {
  return (
    <div>
      <p>{t('welcome_msg')}</p>
      <p>{t('secondary:hello_world')}</p>
    </div>
  );
}

export async function getStaticProps({ locale }) {
  return {
    props: {
      i18nResources: await getTranslations(locale, ['default', 'secondary'])
    }
  };
}

This seems to work for me. No more warnings and we're able to use useTranslation.

Let me know your thoughts.

@dcporter44 I'd understood that the benefit of the useTranslation hook was to avoid the need to use prop drilling to pass t down through your component tree.

However, instead of using useTranslation, I've exported t from i18n itself as described in https://github.com/i18next/i18next/issues/1287#issuecomment-718781261 and it seems to be working nicely with your solution here in both dev and prod (on Vercel).

Thanks for all your work on this - super helpful.

@stephent Ah, yeah - good point. I didn't consider the prop drilling. Yeah, it looks like just using t from the i18n instance is the way to go.

@dcporter44 Any example on how I could make this running now while still using getInitialProps method instead of getStaticProps.

Also, from the code snippets you shared I don't see how we can preload a namespace, like for eg. common for each page?

Thanks for the good work and support!

Quick update

Hi all – want to check in again. Apologies for a delay in response, I have been quite busy in the past few weeks.

I want to clarify that next-i18next will definitely be proceeding with a provider-based approach to support the maximum number of use cases. If individuals want to explore other options, I encourage them to, but please let's keep discussion in this issue centred around next-i18next.

After some consideration, and some discussion with the Vercel team and various NextJs contributors, I have decided to simply release the current work on a beta branch, as I do believe it's ready for testing.

In the end, how the NextJs core handles locale detection and fallback is likely going to be an ongoing process, as the Vercel team builds out parity with the rest of the industry.

next-i18next v8 beta release

I am going to stop all active development on next-i18next v7, outside critical security issues, and start releasing a beta that supports NextJs v10 on [email protected]. That source code can be found on the next-v10 branch.

You can install this via yarn add next-i18next@beta and try it out right away!

Also, I have updated this example repo to use [email protected], and it is being deployed on Vercel here.

Next steps

While we're ready for early adopters, there is still a lot of work to be done. There is a _lot_ of test coverage to write, edge cases to account for, unexpected behaviour on the Vercel platform, etc.

If anyone is interested in helping, please do reach out.

Quick update

Hi all – want to check in again. Apologies for a delay in response, I have been quite busy in the past few weeks.

I want to clarify that next-i18next will definitely be proceeding with a provider-based approach to support the maximum number of use cases. If individuals want to explore other options, I encourage them to, but please let's keep discussion in this issue centred around next-i18next.

After some consideration, and some discussion with the Vercel team and various NextJs contributors, I have decided to simply release the current work on a beta branch, as I do believe it's ready for testing.

In the end, how the NextJs core handles locale detection and fallback is likely going to be an ongoing process, as the Vercel team builds out parity with the rest of the industry.

next-i18next v8 beta release

I am going to stop all active development on next-i18next v7, outside critical security issues, and start releasing a beta that supports NextJs v10 on [email protected]. That source code can be found on the next-v10 branch.

You can install this via yarn add next-i18next@beta and try it out right away!

Also, I have updated this example repo to use [email protected], and it is being deployed on Vercel here.

Next steps

While we're ready for early adopters, there is still a lot of work to be done. There is a _lot_ of test coverage to write, edge cases to account for, unexpected behaviour on the Vercel platform, etc.

If anyone is interested in helping, please do reach out.

Thank you for the update.

I've reviewed the code, it looks GOOD!
Just to clarify that I got it properly, on each page there is a need to call to serverSideTranslations and pass it as props manually, correct?

@felixmosh Thanks for the review!

And yes, that is correct. That is more down to Next's paradigms/patterns than anything I would have designed by choice. The fact that they rely on a named export from page-level React components leaves no opportunity to actually write HOCs anymore.

I would be hesitant to say whether it's objectively good or bad, but: especially in the case of SSG where perf is a non-issue, tree traversal to shake out translation keys would be a _much_ better approach.

It can be an HOF ( High order function) which will wrap the getStaticProps / getServerProps but it is not a deal breaker

@felixmosh That's correct, but to be honest I'd rather steer clear of any NextJs internals at this point! I hope that the serverSideTranslations pattern is declarative and clear to users.

@isaachinman Thanks for the update! Code looks great. I didn't mean to steer this issue off-topic. Since Next now handles so much of the i18n routing and locale delegation, I was trying to understand the value of using next-i18next instead of just using react-i18next or just i18next. Personally I'll end up using the beta you've just released because it's less code management on my end and I trust your experience with i18next integration. Hopefully I can help out with writing some of the tests like you've mentioned.

But I'm curious if you see any major performance benefits using the HOC-based beta you've just released versus just using react-i18next or i18next by itself (like in my basic examples above).

@dcporter44 In general, next-i18next should not alter perf in any way, besides the few added kb to your JS bundle. You can see from the report here that the actual size of the next-i18next@beta source code is currently ~2.2kb GZIP, and could probably be reduced even further.

Tried the beta and works mostly well 👍 Thank you! 🙏

It looks like fallbacks don't work. So if my default language is en and I use de and I lack a translation in de it doesn't substitute in the en translation.

Also a question: Is there still a way to programatically change the locale (without using routing)?

@janhesters Thanks for testing!

Locale fallback

We are still setting fallbackLng in the i18next config. If fallback behaviour is somehow different between master and next-v10 branches, then I've done something wrong. Do you have time to quickly debug a bit, and see if you can identify the problem?

Changing language imperatively

Have you checked the docs?

Their imperative example is as such:

router.push('/another', '/another', { locale: 'fr' })

@isaachinman I'm doing some testing. Everything seems to work great.

Do you see any reason that we can't just get rid of defaultLocale and locales as options in next-i18next.config.js, and instead just use the values provided to next.config.js? I don't see the benefit in writing it in 2 places, but maybe I'm missing something.

You could pass the entire router object to getServerSideTranslations instead of just passing in the locale. And then you could pass that router object into createConfig. In createConfig you would then have access to Next's locale, defaultLocale, and locales.

In addition, if we make the localePath property default to '/public/static/locales', we could make using next-i18next.config.js entirely optional.

I also wanted to ask, which next-i18next.config.js options are still able to be used? This would be good start for the README. For example, the use option is no longer able to be used because functions are not able to be serialized as JSON in getStaticProps. I would make a PR for this, but I'm not sure what you had in mind.

@isaachinman

Locale fallback

I didn't check in production, only development. That is probably the reason. False alarm, sorry.

Changing language imperatively

I missed that. Thank you!

@janhesters Thanks for testing!

Locale fallback

We are still setting fallbackLng in the i18next config. If fallback behaviour is somehow different between master and next-v10 branches, then I've done something wrong. Do you have time to quickly debug a bit, and see if you can identify the problem?
```

My understanding is that you use _lngsToLoad_ in the master version to select which languages shall be kept in the store. However in the beta version it is gone and serverSideTranslations only keeps the data for initialLocale only.

Master version:

const { fallbackLng } = config
const languagesToLoad = lngsToLoad(initialLanguage, fallbackLng, config.otherLanguages)

/*
  Initialise the store with the languagesToLoad and
  necessary namespaces needed to render this specific tree
*/
languagesToLoad.forEach((lng) => {
  initialI18nStore[lng] = {}
  namespacesRequired.forEach((ns) => {
    initialI18nStore[lng][ns] = (
      (req.i18n.services.resourceStore.data[lng] || {})[ns] || {}
    )
  })
})

Beta version

namespacesRequired.forEach((ns) => {
    initialI18nStore[initialLocale][ns] = (
      (i18n.services.resourceStore.data[initialLocale] || {})[ns] || {}
    )
  })

@dcporter44 Actually, it occurs to me that we can go one step further and just remove locales and defaultLocale entirely.

Since next-i18next@beta only ever initialises i18next instances for a single locale at a time, we don't even need to be aware of those other values, unless I am forgetting some use case. Check out the idea, here.

This would also allow users to completely omit the next-i18next.config.js file unless they had custom/non-standard configuration to add.

For example, the use option is no longer able to be used because functions are not able to be serialized as JSON in getStaticProps. I would make a PR for this, but I'm not sure what you had in mind.

Not sure I get your point here – we are already serialising the i18next instance in next-i18next@master – this isn't a new problem, right?

Optional next-i18next.config.js is a good idea 👍 I mentioned that before, but I am working on the project where we are using a different locale code for next.js (for example en-NL, nl-NL) and different for locale code for i18n-next (en, nl). I am guessing this is not a common use case, so optional next-i18next.config.js would be awesome :) Once I have some more time, I will try to implement the beta in our project

@isaachinman looks like it won't be possible anymore to use getInitialProps for a page if I want to use this library, correct?

@isaachinman I was testing your new implementation, interestingly locally useTranslation from 'react-i18next' is working with next-18next, not sure if it will in production

@isaachinman I was testing your new implementation, interestingly locally useTranslation from 'react-i18next' is working with next-18next, not sure if it will in production

It should work since the appWithTranslations wraps the app with i18nextProvider

@TheFullResolution Yeah, after looking further into that use case, I just don't think it's something that next-i18next is going to be able to support very well.

@AlexeyToksarov Why do you say that? You should be able to use serverSideTranslations in any of the NextJs data fetching methods.

@TheFullResolution I believe the same is true of previous versions of next-i18next, and yes, it's for the reason that @felixmosh mentioned. We just re-export useTranslation as a utility for users.

@isaachinman because with getInitialProps it throws the Module not found: Can't resolve 'fs' error.

Ah that's correct, we are no longer taking into account the fact that it could run client side as well. What is your use case for using getInitialProps instead of getServerSideProps?

Basically, we just have an existing app and don't really want to rewrite every getInitialProps to getServerSideProps :)

Migrating to NextJs v10 i18n and next-i18next@8 will require a rewrite no matter what!

Yeah, I understand that. I just wanted to say that previously we didn't use the i18n, so in other to use it now we need to rewrite our current data-fetching code which uses getInitialProps.
But hopefully, it shouldn't be too difficult

Thanks

@isaachinman why wouldn't support the use case? we are using our own method to extract the language, so getStaticProps looks like this:

export const getStaticProps: GetStaticProps<Props> = async ({ locale }) => {
    const lng = getLangFromLocale(locale!);
    return {
        props: {
            ...(await serverSideTranslations(lng, ['common', 'home'])),
        },
    };
};

So en-NL is being changed into en by getLangFromLocale, all loads smoothly so far.

Regarding the hook, is it safe to use it then, or is it just a side-effect that can break any moment?


Sorry, I started only now looking at source code. I got confused because types are not completely updated and Typescript was screaming you do not have any exported member 'useTranslation'.

I checked and the problem seems to be the fact you export types, but not methods in types.d.ts

export {
//here we need to add useTranslation, serverSideTranslations
  I18nContext,
  withTranslation,
}

I tested the beta version, successfully build the app, and deployed it. Translations working without a problem, our language switcher which calls nextjs router router.push('/', '/', { locale:${language}-${country}}); successfully changes locale and page displays new language afterwards.

So other than outdated types I see no issues with the new version, good job! 👍

@TheFullResolution Ah, that makes sense. If you're handling locale resolution yourself, there shouldn't be any issues.

I just want to note that I've made some changes to types and documentation, and have updated the README.

I've released these changes on [email protected], which can be installed via yarn add next-i18next@beta.

If anyone can have a look at the new README and give thoughts, and continue to test the beta, I would appreciate that. Test coverage (both unit and e2e) is the main missing piece now, before considering a production-ready release.

@isaachinman When I try to test out the beta version I'm not able to fulfill the types definition expectations:

Could not find a declaration file for module 'next-i18next/serverSideTranslations'. './frontend/node_modules/next-i18next/serverSideTranslations.js' implicitly has an 'any' type.
Try npm i --save-dev @types/next-i18next if it exists or add a new declaration (.d.ts) file containing declare module 'next-i18next/serverSideTranslations';ts(7016)

Not sure why we need @types at all as the project is TS based already

@kachar Ah, thanks for catching that. You are correct, this package exports its own types, and there is no such thing as @types/next-i18next.

I've added a type for next-i18next/serverSideTranslations and have released [email protected]. Can you please have a look and let me know if that resolves the issue for you?

@isaachinman It looks the serverSideTranslations file is being found correctly, no error is being thrown. Anyhow it doesn't show the actual types exported from the function.

I've found that the other dependency appWithTranslation cannot be found in the package.

import { appWithTranslation } from 'next-i18next'

image

Yes, I also see serverSideTranslations as a valid import however since npm included only content of dist folder with compiled javascript code export { serverSideTranslations } from './src/serverSideTranslations' only prevents import error but doesn't provide types.

Also, appWithTranslation is missing from types.d.ts export, would be enough to just add it like this:

const  appWithTranslation: AppWithTranslation

export {
  I18nContext,
  useTranslation,
  withTranslation,
  appWithTranslation
}

For the rest, I still didn't find any issues with the beta version (I checked the latest version as well)

@isaachinman there is a typo in the "Advanced Configuration" section of the updated README file. There it says the config file shall be named next-i18next-config.js. According to src/serverSideTranslations.ts it's supposed to say next-i18next.config.js. (.config.js instead of -config.js).

Hey all, I found something today while playing with / migrating to the beta.2. I am trying to use a remote backend for the translations, and per the docs (I think), all other i18next configs should go in the next-18next.config.js file at root. However, when I plug in the 18next-locize-backend, I get this error:

// next-i18next.config.js
const Locize = require('i18next-locize-backend');

module.exports = {
  ...
  backend: { ... },
  use: [Locize],
};
// error
SerializableError: Error serializing `._nextI18Next.userConfig.use[0]` returned from `getServerSideProps` in "/".
Reason: `function` cannot be serialized as JSON. Please only return JSON serializable data types.

I apologize if this is just user error, but after a couple hours tinkering around I haven't been able to figure it out, so I wanted to throw it in here in case it helps.

I really appreciate this library, and the direction it's going. Thanks so much!

Edit: I really couldn't narrow down if this was a bug with next-18next, or with my implementation. Anyway, realized I probably didn't want to use Locize anymore, so I gave up my scavenger hunt to contribute for this fix. If anybody else is curious or interested, the last thing I thought was important was that this config worked before -beta.2, so I was focused mostly on identifying why it was failing to work. If I removed the .use property at the getServerSideProps, I could get the error to go away, but (perhaps unsurprisingly), translations wouldn't work. Sorry this wasn't very useful, or productive!

Just trying new -beta.2 and everything is working, no big issues so far. I can confirm that appWithTranslation is missing from export, manually adding it to types.d.ts export is enough though.

Also I let you know that Trans component from react-i18next is working correctly. It just need to be imported from react-i18next package (no next-i18next).

I've installed beta.2 and the solution to add appWithTranslation to types.d.ts works, but now I'm getting the following error:

error - ./node_modules/next-i18next/dist/commonjs/utils/consoleMessage.js:51:0
Module not found: Can't resolve 'util'

Could it have to do with that we opt to run it serverless?

Does someone have a working (or close to) repo with the beta.2 version that's a bit more complex than the example here? Maybe with server, etc? Thanks! We want to migrate from 6 to 7, but need a basis so I can understand how to proceed with regards to actually getting the translations

It was possible to use next-i18next v7 without locale routing. It's not clear to me if this is still supported in v8.

When I add i18n config to next.config.js, nextjs automatically redirects to the path containing the locale (unless it's the default locale).

This feature can be disabled by setting i18n.localeDetection to false in next.config.js, but this disables detection and routing, not just routing.

When localeDetection is set to false Next.js will no longer automatically redirect based on the user's preferred locale and will only provide locale information detected from either the locale based domain or locale path as described above.

https://nextjs.org/docs/advanced-features/i18n-routing#disabling-automatic-locale-detection

I am currently testing out the beta on a project, and it seems to be working very well!

As others have pointed out a few types are missing, but I can live with that for now. In any case, I have never developed a library myself, but is there not a way to generate these types automatically (since the library is written in typescript)?

In any way, great work! I wish I could have helped more!

Another thought: I think it would be ideal if the /static/ part of the path to the locales was optional before v8 leaves beta.

/public/ folder support landed in NextJS in v9.1, so it's been out for a while at this point.

Is anyone else having trouble with fallbackLng not working? I followed the conversation above and it seemed to fizzle out, so I'm not sure if this is still an open issue for anyone else, or if we're waiting on changes to Next core. Missing keys aren't falling back to existing ones for me.

After trying out nextjs@v10 and next-i18next@v8, I feel like all the i18n stuff in nextjs is fairly unstable.

In particular, there seem to be some inconsistencies in how the locales are handled in the URLs. I opened a new issue in the nextjs repo, and there are a handful of other open issues all related to redirects and i18n.

I'm not certain what the URL structure is for urls that should have both a basePath and a locale in them... is it /{locale}/{basePath}/{page} or /{basePath}/{locale}/{page}? I think this question might be behind at least a few of the issues below.

Overall I feel that v8 should maybe stay beta until the dust has settled with the new i18n stuff at NextJS.

Related issues:

Now that next-i18next will be relying on nextjs for determining the language, there is a "standard" way to change language and get language configuration information (the router).

Currently we can get this information from nexts router, which is fine, but it could be a little confusing to new users, especially when you have to do something like this:

router.push(router.route, router.asPath, { locale: language });

Should next-i18next also export a more straightforward way of doing this?
Is there a demand for using the NEXT_LOCALE cookie? I personally didn't even know it was a thing until yesterday.

I think there is a opportunity to abstract a series of things here.

I'm currently using something similar to this:

import { useRouter } from "next/router";
import { useCallback } from "react";
import { useCookies } from "react-cookie";

const ONE_YEAR = 365 * 24 * 60 * 60;

export function useLanguage(cookieMaxAge = ONE_YEAR) {
  const router = useRouter();
  const [_, setCookie] = useCookies(["NEXT_LOCALE"]);

  const changeLanguage = useCallback(
    (language: string) => {
      router.push(router.route, router.asPath, { locale: language });
      setCookie("NEXT_LOCALE", language, {
        sameSite: true,
        maxAge: cookieMaxAge,
      });
    },
    [cookieMaxAge, router, setCookie]
  );

  return {
    language: router.locale!,
    languages: router.locales!,
    defaultLanguage: router.defaultLocale!,
    changeLanguage,
  };
}

Note: universal-cookie is used internally in react-cookie it could be used directly if bundle size is a concern, as it is 90% lighter.

@joaomanke I like that API for accessing/changing the language. This should make it accessible through getServerSideProps in the same way as the example shows right? Also, why not just set the Accept-Language cookie? Safety?

The hook will likely not work in getServerSideProps, also I don't think you should change language during getServerSideProps.

The i18n options are already available from the getXXXProps function parameter, in the end it would be only changing the names of the properties.

Thinking about it maybe then there should be a single word for "language", i18n uses "language", nextjs uses "locale", since next-18next is trying to abstract i18next away I think it should use nextjss vocabulary where possible.

The hook that I exemplified above maybe should be named useLocale, returning { locale: router.locale!, locales: router.locales!, defaultLocale: router.defaultLocale!, changeLocale } .

@filipesmedeiros To be honest, I don't for sure know why use NEXT_LOCALE rather then Accept-Language, but I think Accept-Language is more of a base configuration from the browser, while NEXT_LOCALE is a user preference for the website.

Yeah I didn't mean using the hook inside getServerSideProps but rather if that router.push would trigger Next to pass the correct locale config (names, etc) on the next page load, in the gSSP, which I guess you answered that it does!

That's nice, I like that. Then you just need to use the new getServerSideTranslation from next-i18next to get whatever you want, or use the browser instance if stuff is loaded there, right?

That's nice, I like that. Then you just need to use the new getServerSideTranslation from next-i18next to get whatever you want, or use the browser instance if stuff is loaded there, right?

Yep

I definitely think that's the way to go. Let Next take care of everything that's not "content management/loading", as much as possible.

Hi all, just checking in. Sorry for my absence, I've had a very busy few weeks working on commercial stuff.

I hope to spend a few hours reading through the comments and suggestions here, this coming weekend.

I would like to remind everyone that they can get involved and actively help with the beta effort, by opening PRs against the next-i18next@next-v10 branch.

I would really appreciate help with:

  1. Correct TS typings
  2. Remove /static/ from path to locales
  3. Unit and e2e test coverage

Helloa,

I was wondering if there is a good way to get all translation namespaces for serverSideTranslations or alternatively only load the needed namespaces dynamically?

My use case is a bit convoluted but in essence, I am loading content components dynamically (potentially out of a selection of 1000 or so). In an ideal world, I'd only load the required translation file, once I load the component. Another option would be to just load all translation files initially, but to my understanding, serverSideTranslations requires to explicitly name the namespaces.

Cheers

Hey @isaachinman what do you think of including a hook like what @joaomanke proposed as part of the new API? And then we get basically:

  1. getServerSideTranslations for server side fetching of translation based on locale
  2. useLocale for changing it imperatively (and accessing info if needed)
  3. appWithTranslation for accessing the browser instance and loading translations on the client side

EDIT: just noticed it's called serverSideTranslations and not get.... I propose a change in the name to be compliant with Next's naming scheme

Hi, I'm thinking about following situation:

I have a page at path /[id] that is automatically statically generated and it fetches data on client side.

To get it translated, I need to provide either getServerSideProps which will disable SSG or getStaticProps, which will enforce using getStaticPaths which will build a new version of page for each id, which is not desired.

Is it possible to get the page translated and still having it statically generated only once for all ids?

@sedlar I'm curious, what are you doing with the ID of you don't use it on a static page? Why do you have a dynamic static page if you don't use the ID?

@filipesmedeiros The ID parameter is used for fetching data from external API on client side.

So why not fetch the translations on the client side also?

@filipesmedeiros Sounds reasonable, could you please point me to some example or docs?
I have the app configured according README and simple omitting the getStaticProps results in translations not working at all. What do I need to add?

appWithTranslations does this, it wraps the app in a i18n instance. I think the current example (maybe the one one next10 branch does it. You just have to access the instance through context, for example, and use the t function. The translations loaded there can be loaded in _app I think

@filipesmedeiros @joaomanke I'm not in favour of handing locale management whatsoever – that would be a step backwards. Any feedback should go directly to the NextJs repo, as they've committed to handling that piece of functionality in a first-class way.

Correct TS types have been released on [email protected]. Thanks @franzmoro!

@isaachinman I understand your position, seems like a nextjs functionality for me as well.

In regards to the typings, even thought they are now up to date, there should be a way to auto generate them.

Since this will be a new major version, there is an opportunity to improve a few things in the dev side of things as well, IMO.
I want to contribute with the following, if you agree I'll have a PR as soon as possible:

  • a more standard build/publish process (I'm looking at TSDX) this would likely:

    • remove the need to manually update the types.d.ts or serverSideTranslations.d.ts files.

    • add treeshaking (possibly enabling import of serverSideTranslations directly from next-i18next

    • reduce bundle size (probably very little)

  • add prettier to the project to enforce formatting (you can define the configurations)

There is a typo in the doc. Custom config is expected to be here ./next-i18next.config.js instead of ./next-i18next-config.js

@joaomanke I agree that our transpilation and type generation setup could be greatly improved. I believe @felixmosh had a look at TSDX, and ran across an issue with multiple entry points. Perhaps you two can sync up, and investigate a potential solution?

@Askadias Do you mind opening a quick PR to fix the docs?

@isaachinman @felixmosh It really seems TSDX don't support multiple entry points, that's a shame, there are apparently a PR (formium/tsdx#367) in which it has been added and is awaiting review/cleanup. But I guess it's not worth it to lock ourselves to a fork when current build process is not broken.

Hey guys, I was trying to follow the example for my NextJS app, but was facing a problem when setting up a dynamic page like below - pages/steps/[slug].ts and depending on the slug I'm showing different components.
Is it possible to include an example of such case? https://github.com/isaachinman/next-i18next-vercel @isaachinman

@nowshadfriday I think you just have to access the params prop (in your getServerSide/StaticProps) and based on that fetch the translations you need. next-i18next only cares about the keys and languages you want.

Error: Error serializing ._nextI18Next.userConfig.interpolation.format returned from getStaticProps in "/about".
Reason: function cannot be serialized as JSON. Please only return JSON serializable data types.

I can't define interpolation.format option in 8.0.0-beta.4.

Can you give a simple example? @isaachinman

Serialisation of functions is an open issue, and is yet to be solved, although there have been a couple helpful suggestions.

@MSefer There is a simple example on the next-v10 branch.

So, I am proposing to solve the issue of serialisation by using serialize-javascript, thus side-stepping NextJs' strict serialisation implementation.

Please checkout #954 and #931 for more info.

Btw @isaachinman I don't know if this has been addressed in this thread but I think not. How should apps handle common translations that are needed in _app (like for layouts)? Should the pages be responsible for requesting the namespace that the _app needs?

@filipesmedeiros Namespaces get passed into the second argument of serverSideTranslations – how you organise translations is up to you. Based on what you've just described, yes, you should probably just drop those keys into a common namespace.

But we would have to explicitly pass 'common' on every page, correct (or make an app-land high order function to do it for us)?

Yes. That's not something I would be looking to optimise for, at least not for the initial release.

Update:

  1. I've solved serialisation via serialize-javascript
  2. I've modified the API and configuration slightly (new setup below)
  3. I've added e2e coverage via Cypress, and increased unit test coverage to 96% on next-i18next@beta

I will most likely release next-i18next@beta as [email protected] sometime next week.

I imagine there might be some rough edges, but in general, migration of existing projects should be pretty straightforward.

Here's the new setup:

First, create a next-i18next.config.js file in the root of your project. The syntax for the nested i18n object comes from NextJs directly.

next-i18next.config.js

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'de'],
  },
}

Now, create or modify your next.config.js file.

next.config.js

This tells next-i18next what your defaultLocale and other locales are, so that it can preload translations on the server, etc.

Second, simply pass the i18n object into your next.config.js file, to enable localised URL routing:

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

module.exports = {
  i18n,
}

For full docs, check out the beta README. The latest work has been released under [email protected]. I'm fairly sure the API is stable now.

Hey @isaachinman thanks a lot for this!
I've noted that the modifications from https://github.com/isaachinman/next-i18next/pull/934 are not ported to the beta.

Would you like a PR to do so?

@LeonardDrs Yes, that would be great, thanks!

Okay, doing it right now.
Also sie-note about the new modifications to the config file handling:
This means that all our custom config parameters for next-i18next are also fed to the next.i18n configuration:

next-i18next.config.js

module.exports = {
  i18n: {
    locales: availableLanguages,
    defaultLocale: "fr-FR",
    localePath: path.resolve("some/path/to/locales"),
    localeStructure: "{{lng}}",
    nsSeparator: ":::",
    keySeparator: "::",
    postProcess: "sprintf",
    use: [sprintf],
    ns: "common",
  },
};

next.config.js

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

module.exports = {
  i18n, // This includes also next-i18next custom parameters
}

In this case, should we use this instead ?

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

module.exports = {
  i18n: {
      defaultLocale: i18n.defaultLocale,
      locales: i18n.locales,
  },
}

I feel like it's worth mentioning somewhere

@LeonardDrs No that's incorrect, and definitely a miscommunication on my part. Your setup would look like:

next-i18next.config.js

module.exports = {
  i18n: {
    locales: availableLanguages,
    defaultLocale: "fr-FR",
  },
  localePath: path.resolve("some/path/to/locales"),
  localeStructure: "{{lng}}",
  nsSeparator: ":::",
  keySeparator: "::",
  postProcess: "sprintf",
  use: [sprintf],
  ns: "common",
};

As in, the i18n nested object _only_ contains NextJs specific configuration. We actually destructure it out internally anyways. This is all just to provide an easier way of reusing config for users.

Moreover, I'm fairly sure NextJs will throw if you pass unknown config options into it.

Worth mentioning, you no longer need static/ folder but instead use public/locales/. Was stuck on this for 20 minutes when upgrading from beta.6 to beta.8 lol.

@janhesters Great point, and sorry for catching you on that.

Open request: would anyone be willing to write a migration guide, once we've finalised the API? I think it would be more helpful if this came from an actual user, with an actual project.

Hi all, I believe [email protected] is just about ready to go: #595.

If anyone has a free moment, would you mind reviewing the new README? I am expecting to receive a number of issues immediately following release, and that's fine with a major version like this, but I would like to at least get the documentation correct from the start.

Thank you for your time @isaachinman! Very excited for the 8.0.0 release. Heads up: We didn't get to it yet, but we'll migrate one of our biggest clients to from version 7 to built-in Next.js locale routing + version 8 in the upcoming two weeks. I'll take the time to closely review the new documentation and provide issues and pull requests for any things we'll find out along the way.

Thank you and all of the other contributors very much for the plugin and taking the time to support Next.js version 10!

I find the README good.
There is a paragraph present twice, the one about `useTranslation hook:

You can use NextI18Next.useTranslation hook too! See in react-i18next docs

     // This is our initialised `NextI18Next` instance
     import { useTranslation } from '../i18n'

     ....

There are three functions that next-i18next exports, which you will need to use to translate your project:

If we still want to preserve this part, we should edit the from '../i18n' taken from the example to from 'next-i18next'

Good catch @LeonardDrs – I think that was a result of the merge from master.

I have published [email protected] to npm, and moved next-i18next.com to Vercel.

Thanks everyone!

I'm still not able to get it work with next export with the setup instructed.
This is my workaround https://github.com/isaachinman/next-i18next/issues/10#issuecomment-801954487

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nataliaroque77 picture nataliaroque77  ·  3Comments

isaachinman picture isaachinman  ·  7Comments

sb-bilal-dev picture sb-bilal-dev  ·  6Comments

MichaelIT picture MichaelIT  ·  4Comments

dimensi picture dimensi  ·  4Comments