# ¿Aprovechas TypeScript con i18next?

Si usas [el maravilloso i18next](https://www.i18next.com/) harás esto normalmente:

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

Y en alguna otra parte tendrás un JSON con la traducción asociada a la clave `OPEN`, por ejemplo en español:

```json
{
  "OPEN": "Abrir"
}
```

Esto es maravilloso, pero ¿y si escribo mal la clave? 🤔

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

Lógicamente i18next [viene con declaraciones TypeScript](https://github.com/i18next/i18next/tree/master/typescript) y debería avisarme de que la clave no existe...

... ¿verdad?...

... ¿¡VERDAD!?

![](https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExYzhxZzhlMmh1cHBlbTh3Z2tjM3FjOWtpMjN2eGhzdWEyaXNlcjc1MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l4Jz9evyiEfPEwGis/giphy.gif align="center")

Spoiler: no 🙃

## Cuál es el problema

El problema es que el sistema de tipos no ve tus JSON, que contienen las claves existentes.

Para TypeScript, la función `t` simplemente acepta un parámetro de tipo `string`, algo así:

```typescript
type TFunc = (key: string) => string
```

Y por eso `t('OPEN')` es tan válido como `t('pepe')`, porque ambos valores son `string` y la función acepta `string`. La build pasa ✅ 🤦

Pero claro, si la clave no existe en el JSON al correr la app... no habrá traducción disponible y veremos `pepe` como placeholder 😱

¡PAM! 💥 Otro nuevo bug al backlog 🐛

![](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmJoZWs4cXk2djgwd2pzaHA3cm9weXg0enlwdzd4M3ozb29oNzBsYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Yjvg6BHVXJVOxwrG43/giphy.gif align="center")

Let's fix this forever.

## Función `t` a prueba de balas

Considerando que tenemos este JSON de traducciones al español:

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

Lo que nos interesaría es un tipado más específico que tenga en cuenta las claves:

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

La pregunta es: ¿cómo puedo dopar a mi función `t` para que sepa exactamente qué claves puede recibir?

![](https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExcDN0d2VnZ2dvbXZpMGFpcXMycGh4Nmthd3pzeGJycm5kYjhsaHl3MCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/dcScHZPUskMOJwyqjj/giphy.gif align="center")

Pues [desde la v22.0](https://github.com/i18next/i18next/pull/1775) i18next te ofrece una solución. ¿Lo malo? No es automático. Pero no worries, ¡es muy fácil!

Como se explica [en la documentación](https://www.i18next.com/overview/typescript), creamos un archivo de declaración. Se recomienda colocarlo en `src/@types/i18next.d.ts`

```typescript
// Importamos nuestro JSON para leer las claves
import type jsonKeys from '../i18n/es.json'

// Aumentamos los tipos del módulo i18next
declare module 'i18next' {
  interface CustomTypeOptions {
    resources: {
      // Añadimos las claves JSON en el namespace por defecto
      translation: typeof jsonKeys
    }
  }
}
```

Esto funciona gracias a una característica de TypeScript llamada [aumento de módulos](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation), que permite sobrescribir los tipos de cualquier librería.

La interface `CustomTypeOptions` la han preparado desde i18next para que podamos meter ahí nuestra estructura JSON, y así i18next la tenga en cuenta.

## ¡Ya está!

¿Qué pasa ahora si me equivoco en una clave?

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

✅ Nunca más llegará a producción un *typo* en una clave de traducción.

✅ Además del [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense) mejorado, ya que pulsando `ctrl` + `espacio` dentro de `t('')` recibirás una lista de claves disponibles.

![](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDl6emwwYnJ2MTlyZnlzc3VlMnpwaDFqOHJ0aXoyczcwcHk2aXUxaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/U4DswrBiaz0p67ZweH/giphy.gif align="center")
