i18next / react-i18next

Internationalization for react done right. Using the i18next i18n ecosystem.
https://react.i18next.com
MIT License
9.3k stars 1.03k forks source link

My t function has the return type `never` for all valid values #1811

Open KevinGhadyani-Okta opened 1 week ago

KevinGhadyani-Okta commented 1 week ago

NOTE: I figured out a fix for this bug and want to post it here for posterity as it's not clear in the documentation; also, I think the types changed.

🐛 Bug Report

This function has the type never even though it returns a string (or used to, but I can't compile until I fix this):

const translatedValue = t("table.rowexpansion.collapse")
       // ^? const translatedValue: never

To Reproduce

A codesandbox example that doesn't actually show any text. It always returns undefined, but the type does show never.

Expected behavior

It should return string as my translations are as const:

const translatedValue = t("table.rowexpansion.collapse") // ^? const translatedValue: string

Your Environment

KevinGhadyani-Okta commented 1 week ago

tl;dr

I had to set keySeparator in CustomTypeOptions to false.

The Issue

I found out that keySeparator is now in CustomTypeOptions. Issue is that it defaults to ., part of the translation names we're using, but we're not deeply nesting our translation files, they're flat. The docs didn't tell me this anywhere I was looking when trying to figure out how to type it.

Someone setup the project already this way, so that's how it's been. I didn't realize it was ever done differently on other projects. I'm not an i18next expert, so I didn't know the ins and outs of the config. Took me a while to figure out how it's even getting the type info. It was too magic for me until I thought about it.

Deep Explanation

Because of this, the type system was getting messed up:

Key extends `${infer K1}${_KeySeparator}${infer RestKey}`
    ? ParseTReturn<RestKey, Res[K1 & keyof Res], TOpt>
    : // Process plurals only if count is provided inside options

This check in ParseTReturn was doing the first option, not the else clause. Res[K1 & keyof Res] is never.

That leads us to this point in the type:

Res extends readonly unknown[]
    ? // ...
    : Res[Key & keyof Res]

The issue is that Res is never at this point. It's the weirdest thing, but because we're checking a type against never, it returns never and doesn't even hit the true or false values. So I'm seeing never in that case.

adrai commented 1 week ago

@marcalexiei can you confirm? If so, we can add a hint in the docs...

marcalexiei commented 1 week ago

can you confirm?

The code sandbox link is not working: it leads to a 404 page even if I'm logged in. To avoid issue with code sandbox recently policy updates a Github repository where I can easily reproduce the issue would be useful.


I found out that keySeparator is now in CustomTypeOptions

I am using i18next from more than 2 years and I always put all types related configuration inside CustomTypeOptions. On environment info is stated that i18next version is v15.1.1 which seems quite old to me so I assume that something might have changed in > 2 years.


The docs didn't tell me this anywhere I was looking when trying to figure out how to type it.

There is a "Typescript" entry in the left navigation


I found out that keySeparator is now in CustomTypeOptions.

Create a declaration file


Issue is that it defaults to .

There is a table with all available options and their default in the list of CustomOptions values

image

If so, we can add a hint in the docs...

Without a reproduction example where I can see the issue is hard understand what should be added to the documentation. Besides keySeparator is usually a string, setting it as boolean might produce unwanted results.