JCQuintas/ni18n

Vercel: Unable to load translations using getServerSideProps/loadTranslations

atanaskanchev opened this issue ยท 10 comments

While the setup works fine running locally, the deployed to vercel app returns empty translations by loadTranslations in getServerSideProps

GET | https://app.vercel.app/_next/data/HZ7wajlS0AuNQGjuW_9so/en/app/dashboard.json

{"pageProps":{"__ni18n_server__":{"resources":{"en":{"dashboard":{},"common":{}}},"ns":["dashboard","common"]}},"__N_SSP":true}

ni18n.config.ts

import ChainedBackend from 'i18next-chained-backend'
import HttpBackend from 'i18next-http-backend'
import LocalStorageBackend from 'i18next-localstorage-backend'

const isBrowser = typeof window !== 'undefined'

const isRunningOnLocalhost = !!process.env['NEXT_PUBLIC_IS_RUNNING_ON_LOCALHOST']

const localePath = '{{lng}}/{{ns}}.json'

export const ni18nConfig = {
  supportedLngs: ['en'],
  ns: ['common', 'auth', 'dashboard'],
  use: isBrowser ? [ChainedBackend] : undefined,
  defaultNS: 'common',
  fallbackLng: 'en',

  backend: isBrowser
    ? {
        backends: [LocalStorageBackend, HttpBackend],
        backendOptions: [{ expirationTime: 24 * 60 * 60 * 1000 }, { loadPath: `locales/${localePath}` }]
      }
    : isRunningOnLocalhost
    ? { loadPath: `apps/ui-web-app/public/locales/${localePath}` }
    : { loadPath: `locales/${localePath}` },

  interpolation: {
    format: (value, format, lng, options) => {
    ...
    }
  }
}

next.config.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx')

"i18next": "^21.6.5",
"i18next-chained-backend": "^3.0.2",
"i18next-http-backend": "^1.3.1",
"i18next-localstorage-backend": "^3.1.3",
"next": "12.0.7",
"ni18n": "^1.0.3-rc.0",


/**
 * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
 **/

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true'
})

const nextConfig = {
  nx: {
    // Set this to true if you would like to to use SVGR
    // See: https://github.com/gregberge/svgr
    svgr: false
  },
  images: {
    domains: ['cdn.nordigen.com']
  },
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es']
  }
}

module.exports = withBundleAnalyzer(withNx(nextConfig))
const Dashboard: NextPage = () => (
  <AuthGuard>
    <RoutePermissionsGuard>
      <SubscriptionGuard>
        <DashboardLayout>
          <DashboardPage />
        </DashboardLayout>
      </SubscriptionGuard>
    </RoutePermissionsGuard>
  </AuthGuard>
)

export const getServerSideProps = async ({ locale }) => {
  const something = {
    ...(await loadTranslations(ni18nConfig, locale, ['dashboard']))
  }
  console.log('file: index.tsx ~ line 25 ~ getServerSideProps ~ something', JSON.stringify(something))

  return {
    props: something
  }
}

export default Dashboard
 {"i18next": "^21.6.5",
    "i18next-chained-backend": "^3.0.2",
    "i18next-http-backend": "^1.3.1",
    "i18next-localstorage-backend": "^3.1.3",
    "next": "12.0.7",
    "ni18n": "^1.0.3-rc.0"}

image

Hi @atanaskanchev, if the prebuilt JSON is empty it probably means the issue is on the build step.

I would start looking at the path's configuration after the isRunningOnLocalhost conditional.
It is possible you don't need the conditional at all, can you try one of the following solutions?

  1. use undefined
  2. use { loadPath: `apps/ui-web-app/public/locales/${localePath}` }
  3. use { loadPath: `locales/${localePath}` }

If none of these work, would you mind creating a repository where with the reproducible behaviour where I can try to find a solution?

Hi @JCQuintas thanks for the quick response.

I've recreated the issue here https://github.com/atanaskanchev/ni18n-nx-issue and it's deployed to https://ni18n-nx-issue-9y6puo3xe-atanaskanchev.vercel.app/dashboard

localhost
image

vercel
image

vercel workspace
image

In the config, if !isRunningOnLocalhost I am setting the locales path { loadPath: locales/${localePath} } and it seems it matches the vercel workspace

 backend: isBrowser
    ? {
        backends: [LocalStorageBackend, HttpBackend],
        backendOptions: [
          { expirationTime: 24 * 60 * 60 * 1000 },
          { loadPath: `locales/${localePath}` },
        ],
      }
    : isRunningOnLocalhost
    ? { loadPath: `apps/ni18n-nx-issue/public/locales/${localePath}` }
    : { loadPath: `locales/${localePath}` },

So it seems when deploying to vercel, we don't have access to the public folder from the getServerSideProps. :(

The current behaviour work if you are deploying using a docker container though... I've opened a bug issue on nextjs repo and will keep you posted.

A quick solution for your case right now would be to use getStaticProps instead for the time being, or use clientNamespaces to have the translations loaded on the client only.

Hi @JCQuintas I've had a try with v1.0.4-rc.3 but it seems the issue with Vercel still persist.

Indeed, it didn't work. Looking at it deeper, it seems that the issue is on the https://github.com/vercel/nft packager that doesn't allow a "third party module" to flag a file as "necessary" out of its scope. So at the moment we can't really fix that automatically without creating a patch to the nft package and then waiting for vercel to update it.

In the meantime, one workaround would be to create a function on your repository to do that for us

// force-locales-packing.js
import path from 'path'

export const forceLocalesPacking = () => path.join('./public/locales')

Then you should be able to run that inside your getServerSideProps and it will "tag" the files to be bundled. I intend on creating a documentation for this as well as soon as I have time. Then I try to work on a fix on the nft package itself.

Let me know if it works for you, else I can update the repo you linked above with the steps necessary to make it work.

This worked for me.

import type { NamespacesNeeded, Ni18nOptions } from 'ni18n';
import { loadTranslations as ni18nLoadTranslations } from 'ni18n';
import path from 'path';

import { i18n } from '../../next.config';

export const namespaces = [
  'common',
  'register',
  'dashboard',
  'sidebar',
] as const;

export const ni18nConfig: Ni18nOptions = {
  supportedLngs: i18n?.locales,
  ns: namespaces,
};

export const loadTranslations = async (
  initialLocale?: string | undefined,
  namespacesNeeded?: NamespacesNeeded | undefined
) => {
  const locales = path.resolve('./', './public/locales');

  return await ni18nLoadTranslations(
    ni18nConfig,
    initialLocale,
    namespacesNeeded
  );
};

Has anyone found a solution to this?

Has anyone found a solution to this?

This solved my problem #49 (comment)

const locales = path.resolve('./', './public/locales');

Hi @zaniluca, the main issue is on how the nft package decides which file to bundle or not. A discussion regarding that can be found here: vercel/nft#77

I haven't had the time to look at that more in depth and propose a solution, but in the meantime, the solution provided by @RodriguesCosta should work.

Any solution on that?