Do you leverage TypeScript with i18next?

Ismael Ramon
SEAT CODE
Published in
3 min readMay 20, 2024

If you use the wonderful i18next you normally do this:

<button>{t('OPEN')}</button>

And elsewhere you’ve got a JSON with the associated translation to the OPEN key, for example in Spanish:

{
"OPEN": "Abrir"
}

This is wonderful, but what if I make a typo? 🤔

<button>{t('OPEM')}</button>

Obviously i18next comes with TypeScript declarations and it should tell me that the key doesn’t exist…

… right?…

… RIGHT!?

Spoiler: it doesn’t 🙃

What’s the problem

The problem is that the types system can’t see your JSON, which contains the existing keys.

To TypeScript, the t function simply accepts a string type parameter, something like this:

type TFunc = (key: string) => string

That’s why t('OPEN') is as valid as t('pepe'), because both values are string and the function accepts string. The build passes ✅ 🤦

But if the key doesn’t exist in the JSON when running the app… there won’t be an available translation and we’ll see pepe as a placeholder 😱

PAM! 💥 Another new bug to the backlog 🐛

Let’s fix this forever.

Bullet-proof t function

Considering we have this JSON of Spanish translations:

{
"OPEN": "Abrir",
"CLOSE": "Cerrar"
}

We’re interested in more specific types that are aware of the keys:

type TFunc = (key: 'OPEN' | 'CLOSE') => string

The question is: how can I dope my t function so that it knows exactly which keys to accept?

Well, since v22.0 i18next offers a solution. The bad news? It’s not automatic. But no worries, it’s really easy!

As stated in the documentation, we’ll create a declaration file. They recommend placing it in src/@types/i18next.d.ts

// We import our JSON to read the keys
import type jsonKeys from '../i18n/es.json'

// We augment the i18next module types
declare module 'i18next' {
interface CustomTypeOptions {
resources: {
// We add the JSON keys to the default namespace
translation: typeof jsonKeys
}
}
}

This works thanks to a TypeScript feature called module augmentation, which allows overwriting the types from any package.

The CustomTypeOptions interface has been prepared by i18next so we can place our JSON structure there, thus i18next can use it.

It’s done!

What happens now if I make a typo in a key?

<button>{t('OPEM')}</button>
// ❌ Argument of type '["OPEM"]' is not assignable to
// parameter of type '[key: "OPEN" | "CLOSE"]'

✅ A typo in a translation key will never again reach production.

✅ Apart from the improved IntelliSense, since pressing ctrl + space within t('') will throw a list of available keys to you.

--

--