i18next/react-i18next

Type error with i18next@23.7.2+

rktyt opened this issue · 26 comments

rktyt commented

🐛 Bug Report

The following error occurs:

error TS2741: Property 'reportNamespaces' is missing in type 'import("/tmp/test/node_modules/i18next/index").i18n' but required in type 'import("/tmp/test/node_modules/i18next/index").i18n'.

More details:
スクリーンショット 2023-11-13 18 54 36


No errors with i18next versions 27.6.0, 27.7.0, 27.7.1.
I’m got errors with i18next versions 27.7.2, 27.7.3.

To Reproduce

The following three files are required to reproduce.

index.tsx:

import { createInstance } from 'i18next'
import type { ReactNode } from 'react'
import { initReactI18next, I18nextProvider } from 'react-i18next'

const i18n = createInstance()
await i18n.use(initReactI18next).init({
  lng: 'en',
  fallbackLng: 'en',
  ns: ['common'],
  defaultNS: 'common',
  debug: false,
  interpolation: {
    escapeValue: false,
  },
  resources: { en: { common: {} } },
})

export function I18nextTestStubProvider({ children }: { children: ReactNode }) {
  return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
}

package.json:

{
  "dependencies": {
    "i18next": "23.7.3",
    "react": "^18.2.0",
    "react-i18next": "^13.4.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.37",
    "typescript": "^5.2.2"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "strictNullChecks": true,
    "removeComments": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": "."
  }
}

After creation, check with npm install && npx tsc --noEmit

Expected behavior

No type errors.

Your Environment

  • runtime version: node v18
  • i18next version: 23.7.3
  • os: Linux

Additionnal context

Probably the same as #1379.
However, the cause seems to be different because multiple versions of i18next are not installed.

adrai commented

Can you provide a reproducible example please?

rktyt commented

@adrai
I've updated it to include everything you need to reproduce it.

Same here 🙋‍♂️

I also created a minimum reproduction:
https://stackblitz.com/edit/vitejs-vite-vpz18f?file=src%2Fmain.ts

Screenshot
adrai commented

seems this is because of the moduleResolution set to bundler... if you change it to NodeNext, it works

@adrai remix-utils requires "moduleResolution": "Bundler" in tsconfig to resolve ESM/CJS conflicts.

adrai commented

If you have an idea on how to "fix" this, feel free to provide a PR.

cc: @sergiodxa (the author of remix-i18next)

adrai commented

v13.4.1 should be better

adrai commented

i18next v23.7.6 + react-i18next v13.4.1

adrai commented

Alternatively, try to cast it:

import { createInstance, i18n as I18N } from "i18next";
import { useTranslation } from "react-i18next";

const i18n = createInstance();
useTranslation("test", { i18n: i18n as I18N });

Still a type issue in 23.7.6 and 13.4.1 when setting the <I18NextProvider i18n={instance}/>

Is there something I can add to the react-i18next.d.ts file?

This is what I have now.

import type resources from '~/languages/en';
import 'react-i18next';

declare module 'i18next' {
    interface CustomTypeOptions {
        resources: typeof resources;
    }
}

Everything still works as expected when I @ts-ignore the type error.

// @ts-ignore
<I18nextProvider i18n={instance}>
adrai commented

Did you try to cast it? Like here: #1693 (comment)

Yes, but it doesn’t resolve the type error with the Provider i18n prop. The example above is used with the useTranslation hook, but that isn’t an issue in remix. Just the provider.

adrai commented

Did you try this?

import { i18n as I18N } from "i18next";
// ...
<I18nextProvider i18n={instance as I18N}>
adrai commented

This works for me:

import { createInstance, i18n as I18N } from 'i18next'
import type { ReactNode } from 'react'
import { initReactI18next, I18nextProvider } from 'react-i18next'

const i18n = createInstance()
i18n.use(initReactI18next).init({
  lng: 'en',
  fallbackLng: 'en',
  ns: ['common'],
  defaultNS: 'common',
  debug: false,
  interpolation: {
    escapeValue: false,
  },
  resources: { en: { common: {} } },
})

export function I18nextTestStubProvider({ children }: { children: ReactNode }) {
  return <I18nextProvider i18n={i18n as I18N}>{children}</I18nextProvider>
}

In remix-i18next, on the entry.server file, you do this:

https://github.com/sergiodxa/remix-i18next#server-side-configuration

let instance = createInstance();
  let lng = await i18next.getLocale(request);
  let ns = i18next.getRouteNamespaces(remixContext);

  await instance
    .use(initReactI18next) // Tell our instance to use react-i18next
    .use(Backend) // Setup our backend
    .init({
      ...i18n, // spread the configuration
      lng, // The locale we detected above
      ns, // The namespaces the routes about to render wants to use
      backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") },
    });

  return new Promise((resolve, reject) => {
    let didError = false;

    let { pipe, abort } = renderToPipeableStream(
      <I18nextProvider i18n={instance}>
        <RemixServer context={remixContext} url={request.url} />
      </I18nextProvider>,

I tried i18n={instance as I18N} but the error persists.

adrai commented

You're still not casting the instance in your code.

Instead of:

<I18nextProvider i18n={instance}> <RemixServer context={remixContext} url={request.url} /> </I18nextProvider>,

Do:

<I18nextProvider i18n={instance as I18N}> <RemixServer context={remixContext} url={request.url} /> </I18nextProvider>,

That’s not my code, I pasted the code example from the docs I linked to. Sorry for the confusion I was trying to add more context.

In my code, I cast it as I mentioned:

I tried i18n={instance as I18N} but the error persists.

adrai commented

So can you please provide your complete reproducible Code repository?
Or keep the ts-ignore 🤷‍♂️

Can’t do the first, unfortunately. I’ll keep the ts-ignore but other remix-i18next users may run into the same issue so I’ll have Sergio make a note about it.

Got it working with the casting.

tgds commented

this module augmentation is the problem:

reportNamespaces: ReportNamespaces;

the property should be optional. however, changing it to optional might be a breaking change?

adrai commented

v14.0.0 has just been released that should make it optional

tgds commented

v14.0.0 has just been released that should make it optional

thanks for the super quick release!