¿Aprovechas TypeScript con i18next?

¿Aprovechas TypeScript con i18next?

Si usas el maravilloso i18next harás esto normalmente:

<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:

{
  "OPEN": "Abrir"
}

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

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

Lógicamente i18next viene con declaraciones TypeScript y debería avisarme de que la clave no existe...

... ¿verdad?...

... ¿¡VERDAD!?

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í:

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 🐛

Let's fix this forever.

Función t a prueba de balas

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

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

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

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?

Pues desde la v22.0 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, creamos un archivo de declaración. Se recomienda colocarlo en src/@types/i18next.d.ts

// 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, 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?

<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 mejorado, ya que pulsando ctrl + espacio dentro de t('') recibirás una lista de claves disponibles.