tutoriales.com

Internacionalización (i18n) en React Native: Creando Apps Multilingües

Guía completa paso a paso para añadir soporte multiidioma a tus aplicaciones React Native. Descubre cómo configurar i18next, gestionar archivos de traducción y cambiar de idioma de forma dinámica sin reiniciar la app.

Intermedio8 min de lectura7 views
Reportar error

🌍 Introducción a la Internacionalización en React Native

En el ecosistema actual de desarrollo móvil, las aplicaciones ya no tienen fronteras. Crear una aplicación que solo soporte un idioma limita drásticamente su alcance potencial. La internacionalización (a menudo abreviada como i18n) es el proceso de diseñar y preparar tu aplicación para que pueda adaptarse a diferentes idiomas y regiones sin necesidad de modificar el código fuente.

En este tutorial aprenderás a integrar una solución robusta utilizando i18next y react-i18next en un proyecto de React Native. Construiremos una interfaz que detectará el idioma del dispositivo y permitirá al usuario cambiar entre español, inglés y francés de manera fluida.

💡 Consejo: Es una excelente práctica planificar la internacionalización desde el primer día de desarrollo en lugar de intentar traducirlo todo al final del proyecto.

🛠️ Prerrequisitos y Configuración del Entorno

Para seguir este tutorial con éxito, necesitarás:

  • Tener instalado Node.js y un entorno funcional de React Native (CLI o Expo).
  • Familiaridad básica con componentes de React Native como Text, View y TouchableOpacity.

Primero, vamos a instalar las librerías principales que gestionarán las traducciones y la detección del idioma del dispositivo:

npm install i18next react-i18next
npm install @react-native-async-storage/async-storage
npm install react-native-localize
  • i18next: El núcleo de internacionalización que maneja la lógica de traducciones, pluralización y formatos.
  • react-i18next: Los enlaces oficiales (bindings) para React, permitiendo usar ganchos (hooks) como useTranslation.
  • AsyncStorage: Nos permitirá guardar la preferencia de idioma del usuario.
  • react-native-localize: Utilidad para detectar el idioma configurado en el sistema operativo del usuario.

🏗️ Estructura de Archivos y Traducciones

Una buena organización del código es clave para mantener un proyecto escalable. Crearemos una carpeta dedicada para la configuración de la internacionalización.

Estructura de i18n en React/JS src / locales / es / translation.json en / translation.json fr / translation.json i18n.js (Configuración) importa & inicializa i18next Core Library Flujo de Datos: 1. Los JSON contienen las llaves. 2. i18n.js agrupa los recursos. 3. i18next provee la traducción.

Crea una estructura de carpetas como la siguiente dentro de la raíz de tu proyecto:

src/
  └── locales/
      ├── es/
      │   └── translation.json
      ├── en/
      │   └── translation.json
      └── i18n.js

Archivo de Traducción en Español (src/locales/es/translation.json)

{
  "welcome": "¡Bienvenido a nuestra aplicación!",
  "description": "Esta aplicación demuestra cómo implementar internacionalización en React Native.",
  "changeLanguage": "Cambiar Idioma",
  "currentLanguage": "Idioma actual: Español"
}

Archivo de Traducción en Inglés (src/locales/en/translation.json)

{
  "welcome": "Welcome to our app!",
  "description": "This app demonstrates how to implement internationalization in React Native.",
  "changeLanguage": "Change Language",
  "currentLanguage": "Current language: English"
}

⚙️ Configurando i18next y React Native Localize

Ahora vamos a escribir el archivo de configuración central src/locales/i18n.js. Aquí inicializamos i18next, configuramos el idioma por defecto y conectamos el detector de idioma nativo.

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from 'react-native-localize';
import AsyncStorage from '@react-native-async-storage/async-storage';

import es from './es/translation.json';
import en from './en/translation.json';

const LANGUAGES = {
  es,
  en,
};

const LANG_CODES = Object.keys(LANGUAGES);

// Detector de idioma basado en el dispositivo
const languageDetector = {
  type: 'languageDetector',
  async: true,
  detect: async (callback) => {
    try {
      // Revisar si el usuario ya guardó una preferencia
      const savedLanguage = await AsyncStorage.getItem('@user-language');
      if (savedLanguage) {
        return callback(savedLanguage);
      }

      // Si no hay preferencia guardada, usar el idioma del dispositivo
      const bestLanguage = RNLocalize.findBestLanguageTag(LANG_CODES);
      
      callback(bestLanguage?.languageTag || 'es');
    } catch (error) {
      console.log('Error leyendo el idioma', error);
      callback('es');
    }
  },
  init: () => {},
  cacheUserLanguage: async (language) => {
    try {
      await AsyncStorage.setItem('@user-language', language);
    } catch (error) {
      console.log('Error guardando el idioma', error);
    }
  },
};

i18n
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    compatibilityJSON: 'v3',
    resources: LANGUAGES,
    react: {
      useSuspense: false,
    },
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;
⚠️ Advertencia: No olvides importar tu archivo de configuración `i18n.js` en el punto de entrada principal de tu aplicación (por ejemplo, `App.js` o `index.js`) para que se ejecute al arrancar la app.

📱 Implementación en Componentes con useTranslation

Una vez configurado i18next, utilizar las traducciones en tus componentes es extremadamente sencillo gracias al gancho useTranslation.

import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, SafeAreaView } from 'react-native';
import { useTranslation } from 'react-i18next';
import './src/locales/i18n'; // Importante para inicializar i18n

export default function App() {
  const { t, i18n } = useTranslation();

  const changeLanguage = (langCode) => {
    i18n.changeLanguage(langCode);
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.content}>
        <Text style={styles.title}>{t('welcome')}</Text>
        <Text style={styles.description}>{t('description')}</Text>
        
        <View style={styles.languageBox}>
          <Text style={styles.langText}>
            {t('currentLanguage')} ({i18n.language.toUpperCase()})
          </Text>
        </View>

        <Text style={styles.sectionTitle}>{t('changeLanguage')}</Text>
        
        <View style={styles.buttonContainer}>
          <TouchableOpacity 
            style={[styles.button, i18n.language === 'es' && styles.activeButton]}
            onPress={() => changeLanguage('es')}>
            <Text style={styles.buttonText}>Español</Text>
          </TouchableOpacity>

          <TouchableOpacity 
            style={[styles.button, i18n.language === 'en' && styles.activeButton]}
            onPress={() => changeLanguage('en')}>
            <Text style={styles.buttonText}>English</Text>
          </TouchableOpacity>
        </View>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f6fa',
  },
  content: {
    flex: 1,
    padding: 24,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 26,
    fontWeight: 'bold',
    color: '#2f3640',
    textAlign: 'center',
    marginBottom: 12,
  },
  description: {
    fontSize: 16,
    color: '#718093',
    textAlign: 'center',
    marginBottom: 24,
  },
  languageBox: {
    backgroundColor: '#e1b12c',
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderRadius: 8,
    marginBottom: 32,
  },
  langText: {
    color: '#fff',
    fontWeight: '600',
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    marginBottom: 16,
    color: '#2f3640',
  },
  buttonContainer: {
    flexDirection: 'row',
    gap: 12,
  },
  button: {
    backgroundColor: '#718093',
    paddingVertical: 12,
    paddingHorizontal: 20,
    borderRadius: 8,
  },
  activeButton: {
    backgroundColor: '#44bd32',
  },
  buttonText: {
    color: '#fff',
    fontWeight: 'bold',
  },
});

🔄 Manejo de Plurales y Variables Dinámicas

Las aplicaciones reales a menudo requieren insertar variables dentro de los textos traducidos o manejar reglas de pluralización complejas (por ejemplo: "1 mensaje nuevo" vs "5 mensajes nuevos").

Interpolación de Variables

Puedes pasar variables directamente a la función de traducción t() utilizando un objeto como segundo parámetro:

// En translation.json
{
  "greeting": "Hola, {{name}}! Tienes {{count}} notificaciones."
}
// En tu componente
<Text>{t('greeting', { name: 'Carlos', count: 3 })}</Text>

Pluralización Automática

i18next soporta pluralización nativa basada en sufijos como _one y _other:

// En translation.json
{
  "itemCount_one": "{{count}} elemento encontrado",
  "itemCount_other": "{{count}} elementos encontrados"
}
// Uso en componente (detecta automáticamente el sufijo basado en la variable count)
<Text>{t('itemCount', { count: 1 })}</Text>
<Text>{t('itemCount', { count: 5 })}</Text>

❓ Preguntas Frecuentes (FAQ)

¿Cómo soportar textos en formato RTL (Right-to-Left) como el Árabe o Hebreo? React Native provee el módulo `I18nManager` para cambiar la dirección del layout. Puedes utilizar I18nManager.forceRTL(true) cuando el usuario seleccione un idioma que requiera lectura de derecha a izquierda, seguido de un reinicio de la aplicación.
¿Es obligatorio usar AsyncStorage para guardar el idioma? No es estrictamente obligatorio, pero es altamente recomendable para garantizar que la preferencia del usuario se mantenga intacta incluso si cierra completamente la aplicación.

🎯 Conclusión

Has implementado con éxito un sistema de internacionalización completo en tu aplicación React Native. Ahora tu app es capaz de detectar automáticamente la configuración regional del dispositivo, permitir cambios dinámicos de idioma y almacenar las preferencias del usuario de manera persistente.

🔥 Importante: Recuerda revisar periódicamente tus archivos JSON de traducción para asegurarte de que ningún texto estático se quede sin traducir a medida que tu aplicación crezca.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!