gurkerl83 / next-i18next-serverless

8 stars 2 forks source link

i18n.languages array empty/only contains current language #1

Open BjoernRave opened 4 years ago

BjoernRave commented 4 years ago

My i18n.ts file:

import { NextComponentType, NextPageContext } from 'next'
import NextI18Next from 'next-i18next-serverless'
import getConfig from 'next/config'

export const NextI18NextInstance = new NextI18Next(getConfig(), {
  // localePath: typeof window === 'undefined' ? 'public/locales' : 'locales',
  ns: ['common'],
  defaultNS: 'common',
  defaultLanguage: 'es',
  otherLanguages: ['en', 'pt'],
  lng: 'es'
})

/* Optionally, export class methods as named exports */
export const {
  appWithTranslation,
  useTranslation,
  withTranslation,
  i18n
} = NextI18NextInstance
export type I18nPage<P = {}> = NextComponentType<
  NextPageContext,
  { namespacesRequired: string[] },
  P & { namespacesRequired: string[] }
>

With this i18n.ts file, language changing works, but when I log the i18n object I only see the current language in the i18n.languages array. If I use the i18n.ts file from milipede-docs/master and only change the languages, I can't change languages at all, and the array is empty

My next.config.js file:


module.exports = {
...
  publicRuntimeConfig: {
    rootDir: __dirname
  }
...
}

Next.js Version: 9.2.2-canary.12 next-i18next-serverless Version: 1.1.135

gurkerl83 commented 4 years ago

Have you tried the example/simple from the branch where I`m working on? Where in your i18n is allLanguages specified? In mine its set to allLanguages: ['en', 'de'], I suppose it has to be allLanguages: ['es', 'en', 'pt']

BjoernRave commented 4 years ago

I added the allLanguages: ['es', 'en', 'pt'], but same result. I mean everything works for me now in dev, but it seems like there is something wrong there. Also I just deployed to ZEIT Now with @now/next@canary, but in prod I can still see the translation keys, I can't in dev though

gurkerl83 commented 4 years ago

Can you try to use only these lines.

From your code you are exporting a new instance. In my code the getNextI18NextInstance is not exported at all. getNextI18NextInstance is a function wich accepts the runtimeConfig and returns the instance. Consume i18n only through the exports available.

const getNextI18NextInstance = (nextConfig: NextRuntimeConfig) => {
  return new NextI18Next(nextConfig, {
    browserLanguageDetection: false,
    serverLanguageDetection: false,
    partialBundledLanguages: false,
    defaultLanguage: 'en',
    otherLanguages: ['de', 'es', 'pt'],
    lng: 'en',
    allLanguages: ['en', 'de', 'es', 'pt']
  });
};

export const {
  appWithTranslation,
  useTranslation,
  withTranslation,
  i18n
} = getNextI18NextInstance(getConfig());

export const NextI18NextInstance = getNextI18NextInstance(getConfig());
BjoernRave commented 4 years ago

ah, so I shouldn't do const { t, i18n } = useTranslation() , but instead import i18n from the above file?

Tried the above code, no matter where I get i18n from, i18n.languages still only contains i18n.languages

gurkerl83 commented 4 years ago

Also very important for now is the setup of the public folder!

Only do public/locales/en public/locales/de

but not use static within public public ~/static~ /locales/en

BjoernRave commented 4 years ago

yea I am using it as you say. My i18n.ts file is not in the root of the project, but this shouldnt be a problem, or?

gurkerl83 commented 4 years ago

The import has always to grab the i18n either through a relative path import, alias etc. Do not use i18n-next... for that

this will work

import getNextI18NextInstance from '../i18n'; const { i18n, Link, useTranslation } = getNextI18NextInstance; --> do not call the function here with just destructure whats provided

const { t } = useTranslation(['common', 'footer']);

also import { useTranslation } from '../../../../i18n'; const { t } = useTranslation();

of course you have to adjust the path to i18n

To clarify things

most of the time you consume

export const { appWithTranslation, useTranslation, withTranslation, i18n } = getNextI18NextInstance(getConfig());

this is only relevant for _document export const NextI18NextInstance = getNextI18NextInstance(getConfig());

used like composeTestMiddleware(req, res)(nextI18NextMiddleware(NextI18NextInstance));

BjoernRave commented 4 years ago

ah, so I do need to adjust my _document for this to work properly?

gurkerl83 commented 4 years ago

Switching the language would be

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

const { i18n, t } = useTranslation(); i18n.changeLanguage(languageCode);

You can find a reference to switching the lng here

https://github.com/project-millipede/millipede-docs/blob/ab8280a90a148b506d4dd3d4c7b421429605bc87/docs/src/modules/components/LanguageMenu.tsx#L24

gurkerl83 commented 4 years ago

In _document you need


export const composeTestMiddleware = (
  req: IncomingMessage,
  res: ServerResponse
) => (middlewares: Array<Handler<IncomingMessage, ServerResponse>>) => {
  const handler: RequestHandler<IncomingMessage, ServerResponse> = compose(
    middlewares
  );

  const done = () => {
    Logger.log('done');
  };

  handler(req, res, _next => {
    return done();
  });

  return handler;
};

export const instantiateTestMiddleware = (
  req: IncomingMessage,
  res: ServerResponse
) => {
  composeTestMiddleware(req, res)(nextI18NextMiddleware(NextI18NextInstance));
};

And in getInitialProps the following, also here https://github.com/project-millipede/millipede-docs/blob/master/pages/_document.tsx


MillipedeDocument.getInitialProps = async (
  ctx: DocumentContext
): Promise<InitialProps> => {
  instantiateTestMiddleware(ctx.req, ctx.res);

  // Resolution order
gurkerl83 commented 4 years ago

So in the End a crazy setup to keep simple things rolling. In my opinion next-i18next requires a massiv rewrite to make simplification a priority both for internal and API stuff.

I think you will figure it out, I mean you have two repos (the simple example and mine, a much larger one). In any case before integration in you project just try the example and deploy that with now. Everything should work out of the box

BjoernRave commented 4 years ago

@gurkerl83 yea, that seems like a lot of things only to replace some functions with some strings.

I am hoping a bit on what @rdewolff was talking about here https://github.com/isaachinman/next-i18next/issues/274#issuecomment-582582444

BjoernRave commented 4 years ago

but it was already working for me on dev without the things in _document and now I tried to deploy it to ZEIT Now and it break the whole thing

Server side backend2020-02-06T13:18:06.086Z undefined   ERROR   Uncaught Exception  {"errorType":"Error","errorMessage":"ENOENT: no such file or directory, scandir '/var/task/es'","code":"ENOENT","errno":-2,"syscall":"scandir","path":"/var/task/es","stack":["Error: ENOENT: no such file or directory, scandir '/var/task/es'","    at Object.readdirSync (fs.js:854:3)","    at getAllNamespaces (/var/task/frontend/node_modules/next-i18next-serverless/dist/commonjs/config/create-config.js:126:31)","    at _default (/var/task/frontend/node_modules/next-i18next-serverless/dist/commonjs/config/create-config.js:131:27)","    at new NextI18Next (/var/task/frontend/node_modules/next-i18next-serverless/dist/commonjs/index.js:61:46)","    at getNextI18NextInstance (/var/task/frontend/.next/serverless/pages/index.js:1556:10)","    at Object.QJBX (/var/task/frontend/.next/serverless/pages/index.js:1572:5)","    at __webpack_require__ (/var/task/frontend/.next/serverless/pages/index.js:31:31)","    at Module.Y7Z2 (/var/task/frontend/.next/serverless/pages/index.js:2537:66)","    at __webpack_require__ (/var/task/frontend/.next/serverless/pages/index.js:31:31)","    at Module.clG1 (/var/task/frontend/.next/serverless/pages/index.js:3151:22)"]}Unknown application error occurredError
gurkerl83 commented 4 years ago

He just asked the question for a more simplified solution. This really applies to this issue of serverless middleware. First in next-i18next are several dependencies uses e.g. express which is a complete different approach in terms how data flow. Also Next! There are things I like and certain things I hate. On thing what i dislike most is that they provided serverless and lambda far too early and made it a big promise, but backward compatibility fully missing.

gurkerl83 commented 4 years ago

Sorry missed one thing!

you need a file in the following directory, it can be an empty json

./public/locales/en/common.json

It should work now. This is a different topic and probably a bug in NOW itself

BjoernRave commented 4 years ago

@gurkerl83 I have that file

gurkerl83 commented 4 years ago

It seems a problem how you use the i18n file in your index.(ts/js) file

gurkerl83 commented 4 years ago

Please ensure you have no static folder in you root directory

gurkerl83 commented 4 years ago

Maybe you can send a screenshot?

gurkerl83 commented 4 years ago

We can also do a quick skype with screen-sharing, my skype id is project-millipede

BjoernRave commented 4 years ago

yea, would love to do a quick skype session, it's lunch time here now though, so maybe in 1 and a half hours? Btw I am german :)

gurkerl83 commented 4 years ago

Fine with me, have my week of, also German

gurkerl83 commented 4 years ago

Hi, trying to replicate the error, but so far with no success. I looked at the error closer...

Server side backend2020-02-06T13:18:06.086Z undefined ERROR Uncaught Exception {"errorType":"Error","errorMessage":"ENOENT: no such file or directory, scandir '/var/task/es'",

So the first part is a console log of mine in create-config.ts

console.log('Server side backend');

I do not log the directory chosen, but something is off because the filesystem tries to access '/var/task/es'.

'es' is your default language

but it has to be a full path with at least 'locales' mentioned. This is not the case. On the top of the init method the root dir gets a fallback (I think this was required because NOW has several phases when deploying the app and in one of them publicRuntimeConfig is null.

My guess is that something is blocking the runtimeConfig being set at all. If is not set probably the output of process.cwd() will result in '/var/task/' which is certainly the wrong folder the locales are.

let rootDir;

if (runtimeConfig != null && runtimeConfig.publicRuntimeConfig != null) {
  rootDir = runtimeConfig.publicRuntimeConfig.rootDir;
} else {
  rootDir = process.cwd();
} 

So maybe I have to create a stronger condition, not just isServer() but also if runtimeConfig.publicRuntimeConfig != null