i18next/next-i18next

Support for `getServerSideProps`

thomb opened this issue ยท 43 comments

thomb commented

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

When using getServerSideProps as opposed to getInitialProps results in a namespacesRequired warning, despite the fact that I am returning namespacesRequired inside the required props key

I don't know if this is factual or not, or if the warning is a red herring

Describe the solution you'd like

Ideally I'd like getServerSideProps to be supported.

Apologies if this is vague or unclear, or a completely unreasonable request.

Thanks in advance

NextJs 9.3 was released a week ago. None of the new data fetching methods are supported yet, but in general I'd lean towards getStaticProps over getServerSideProps. Typically it's better to associate translation changes with releases, rather than having changes at runtime.

The next-i18next package will be entirely rewritten once NextJs plugin support lands, as quite a few things have changed since its inception.

I'm kind of newbie so sorry for asking this question. As I've figured out your amazing library currently fully supports Next js 9.2, right? Can I start my project using Next js 9.2 and stick to next-i18next 4.2.1 for the next few months?

It fully supports NextJs 9.3 as well, just not the new data fetching methods.

That is entirely false. That'd be a breaking change, and would go against semver. Read the docs.

No deprecations are introduced and getInitialProps will continue to function as it currently does.

It fully supports NextJs 9.3 as well, just not the new data fetching methods.

May I know if there is any schedule for supporting getServerSideProps and getStaticProps?

I do not currently have much time for development on this project due to other factors in life. I don't see this changing for the next few weeks, at least.

As I've stated, next-i18next will be rewritten once plugin support (finally) lands. If you'd like to see this (and many other features) supported, please let the NextJs team know that you are waiting for plugin support!

Just noticed that you can use i18next inside getServerSideProps in the same way as you would do it in the express application, i.e. create & configure plain i18next instance and use it instead

@maxmalov Can you explain what you mean? We still have a dependency on the i18next browser detector middleware.

getServerSideProps is being called on the server-side only, consider the example

import i18next from 'i18next';

const createI18Next = (lang) => {
  // create & configure i18next instance 
}

export const serverSideMethod = ({ params, req, res }) => {
  // detect lang from req somehow (probably can reuse express middleware)
  const lang = detect(req);
  const i18n = createI18Next(lang);
  const { t } = i18n;

  // do whatever you want with t
} 

So later one can use serverSideMethod inside getServerSideProps and get already translated content.

// somewhere in pages directory

import { serverSideMethod } from '...';

export const getServerSideProps = ctx => serverSideMethod(ctx)

export default props => { ... }

For now, it's just a theory. We're working on the AMP first app and have to reuse the existing 18next resources we use in the separate single-page app. So in a few days, we eventually find out whether it works or not

Yes, that approach will work if you want to roll your own implementation, but has nothing to do with this package. Let's keep discussion here on topic.

Any updates here?
It can be a workaround for now, or a way to silence

You have not declared a namespacesRequired array on your page-level component: Page. This will cause all namespaces to be sent down to the client, possibly negatively impacting the performance of your app. For more info, see: https://github.com/isaachinman/next-i18next#4-declaring-namespace-dependencies"

Thanks

Hey @pettomartino - no update from my side. You can always silence warnings by setting strictMode to false. Please do feel free to contribute if you so choose!

@isaachinman thank for so quick reply. Gonna try to implement something then. Any hints?

@pettomartino Do you mean contributing to next-i18next? If we want to support getServerSideProps, we'll need to update the appWithTranslation HOC to also look for that data fetching method in addition to getInitialProps.

@pettomartino there is a demonstration that the i18n solution works in SSG, Hope it helps.
vercel/next.js#11841

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 || [])]
        }
    }
}

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 || [])]
        }
    }
}

this works. thanks!

Is it correct that the defaultProps must be set on the result of the HOC:

This works for me:

const page = withTranslation('toto')(TotoComponent);

page.defaultProps = {
    i18nNamespaces: ['toto','bar']
}

export default page;

This does not:

TotoComponent.defaultProps = {
    i18nNamespaces: ['toto','bar']
}

export default withTranslation('toto')(TotoComponent);

I'm using this version, so that it picks up recursive namespaces from WrappedComponents when using HOC

MyApp.getInitialProps = async (appContext: AppContextType<Router>) => {

    const recursiveNamespaces = (component: NextComponentType & { WrappedComponent?: NextComponentType }, acc: string[] = []): string[] => {

        const ns = component.defaultProps?.i18nNamespaces;
        const a = (typeof ns === "string") ? [...acc, ns] : [...acc, ...(ns || [])];
        let wrappedComponent = component.WrappedComponent;
        if (wrappedComponent) {
            return recursiveNamespaces(wrappedComponent, a)
        } else
            return a
    }

    const appProps = await App.getInitialProps(appContext)
    const namespaces: any = recursiveNamespaces(appContext.Component)
    return {
        ...appProps,
        pageProps: {
            namespacesRequired: [
                ...(appProps.pageProps.namespacesRequired || []),
                ...namespaces]
        }
    }
}

It allows to use your defaultProps like this:

TotoComponent.defaultProps = {
    i18nNamespaces: ['toto','bar']
}

export default withTranslation('toto')(TotoComponent);

@rparree I think you're commenting on the wrong issue. This issue is about getServerSideProps.

@isaachinman I was referring to the comment with the workaround submitted by @icrona above (#652 (comment))

Sorry, what exactly is that workaround achieving?

With the solution in their comment you can use defaultProps to set the namespaces when using a component that uses getServerSideProps. My code looks for defaultProps recursively when using a HOC.

As far as I can tell, that doesn't achieve anything except slightly modifying the API. It won't enable SSG.

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 || [])]
        }
    }
}

how would that work for typescript?

@isaachinman It does the trick for me. I am able to use a component like this (and it will be SSR)

export const getServerSideProps = async function (ctx: NextPageContext) {
  โ€ฆ
 return {
    props: {
      totoProp : ""
   }
 } 
}

TotoComponent.defaultProps = {
    i18nNamespaces: ['toto','bar']
}

export default withTranslation('toto')(TotoComponent);

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 || [])]
        }
    }
}

This approach breaks SSR for me. Only the keys are loaded initially and after a delay they are replaced by the translation.

Edit: nvm this works great, I just missed that the defaultProp is called i18nNamespaces instead of namespacesRequired

To get all namespaces loaded in case none are defined on the page, you would have to adapt icronas solution like this:

NextApp.getInitialProps = async (appContext) => {
  const appProps = await App.getInitialProps(appContext);
  const { defaultProps } = appContext.Component;
  let pageProps = {}
  if(appProps.pageProps.namespacesRequired || defaultProps?.i18nNamespaces) {
    pageProps = {
      namespacesRequired: [...(appProps.pageProps.namespacesRequired || []), ...(defaultProps?.i18nNamespaces || [])],
    }
  }
  return {
    ...appProps,
    pageProps
  };
};

@mattcrn The next-i18next package will already load in all namespaces if you don't define any on the page. Let's keep this issue strictly about the support of getServerSideProps.

@isaachinman yes, but using icrona's solution will break this functionality since it will return an empty array for namespacesRequired and the package is simpy checking if namespacesRequired is defined for loading all namespaces.
So, namespacesRequired: [] means no namespaces are being loaded.

@pettomartino Do you mean contributing to next-i18next? If we want to support getServerSideProps, we'll need to update the appWithTranslation HOC to also look for that data fetching method in addition to getInitialProps.

As far as I understand it, getServerSideProps is not part of the page component, so we would have to create a higher order function to wrap the pages getServerSideProps. Or is there a better way?

I think when this feature lands, it would be ideal to support both getServerSideProps and getStaticProps. For most users of this library, local translations and staticProps will be fine, but the serverSideProps may be useful in projects that are using https://locize.com/ or something similar, where translations are managed outside of the project.

Any update since then ?
Looking forward to have this thing implemented. ๐Ÿ‘

@divlo Feel free to submit a PR.

+1

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 || [])]
        }
    }
}

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?

@SalahAdDin This will be solved by #869.

Fixed in next-i18next@8.0.0.

Thanks a lot for this @isaachinman

@isaachinman Does the new release mean we must deleted the whole workaround for this matter and use the new hook? It is not so much clear for me.

@SalahAdDin Correct, there is no need for a workaround in v8.

Import the serverSideTranslations

import { serverSideTranslations } from "next-i18next/serverSideTranslations";

Now from the getServerSideProps, pass the ..(await serverSideTranslations(locale, ["common"])), with the props.

export const getServerSideProps: GetStaticProps = async ({
  locale,
  locales,
  query
}: any) => {
  return {
    props: {
      ...(await serverSideTranslations(locale, ["common"])),
    }
  }
};

Now add your language string inside
/public/locales/en/common.json

For example

{
  "home": {
    "Test": "Test"
  }
}

You can add more language strings inside the locales directory.

Restart your Next.js app. It will work.