Traducir tu app React es esencial para llegar a una audiencia global. react-intl es una biblioteca de internacionalización potente de FormatJS que proporciona componentes React y una API para formatear fechas, números y cadenas, incluyendo pluralización y manejo de traducciones. Esta guía te guiará a través del proceso de traducir una app React usando react-intl.
Entendiendo la Localización de React con react-intl
react-intl es parte del conjunto FormatJS y proporciona componentes React y una API para internacionalización. Usa formato de mensaje ICU y proporciona excelente soporte para pluralización, formateo de fecha/hora y formateo de números.
Paso 1: Instalar Dependencias
Primero, instala los paquetes necesarios:
npm install react-intl
O con yarn:
yarn add react-intl
Paso 2: Crear Archivos de Traducción
Crea un directorio locales en tu carpeta src y agrega archivos de traducción para cada idioma:
src/locales/en.json (Inglés - por defecto):
{
"app.welcome": "Welcome to our app!",
"button.submit": "Submit",
"button.cancel": "Cancel",
"button.delete": "Delete",
"error.network": "Network error. Please try again.",
"error.notFound": "Page not found",
"items.count": "{count, plural, =0 {No items} one {# item} other {# items}}",
"user.greeting": "Hello, {name}! You have {count, plural, =0 {no messages} one {# message} other {# messages}}.",
"date.today": "Today is {date, date, full}",
"currency.price": "Price: {amount, number, currency}"
}
src/locales/es.json (Español):
{
"app.welcome": "¡Bienvenido a nuestra aplicación!",
"button.submit": "Enviar",
"button.cancel": "Cancelar",
"button.delete": "Eliminar",
"error.network": "Error de red. Por favor, inténtalo de nuevo.",
"error.notFound": "Página no encontrada",
"items.count": "{count, plural, =0 {No hay elementos} one {# elemento} other {# elementos}}",
"user.greeting": "¡Hola, {name}! Tienes {count, plural, =0 {no hay mensajes} one {# mensaje} other {# mensajes}}.",
"date.today": "Hoy es {date, date, full}",
"currency.price": "Precio: {amount, number, currency}"
}
src/locales/fr.json (Francés):
{
"app.welcome": "Bienvenue dans notre application!",
"button.submit": "Soumettre",
"button.cancel": "Annuler",
"button.delete": "Supprimer",
"error.network": "Erreur réseau. Veuillez réessayer.",
"error.notFound": "Page non trouvée",
"items.count": "{count, plural, =0 {Aucun élément} one {# élément} other {# éléments}}",
"user.greeting": "Bonjour, {name}! Vous avez {count, plural, =0 {aucun message} one {# message} other {# messages}}.",
"date.today": "Aujourd'hui est {date, date, full}",
"currency.price": "Prix: {amount, number, currency}"
}
Paso 3: Cargar Mensajes de Traducción
Crea un archivo de utilidad para cargar traducciones:
src/locales/index.js:
import en from './en.json';
import es from './es.json';
import fr from './fr.json';
export const messages = {
en,
es,
fr
};
export const locales = ['en', 'es', 'fr'];
export const defaultLocale = 'en';
O con TypeScript (src/locales/index.ts):
import en from './en.json';
import es from './es.json';
import fr from './fr.json';
export const messages = {
en,
es,
fr
} as const;
export const locales = ['en', 'es', 'fr'] as const;
export const defaultLocale = 'en' as const;
Paso 4: Configurar IntlProvider
Envuelve tu app con IntlProvider en tu componente App principal:
src/App.js:
import React, { useState } from 'react';
import { IntlProvider } from 'react-intl';
import { messages, defaultLocale, locales } from './locales';
import HomePage from './components/HomePage';
function App() {
const [locale, setLocale] = useState(defaultLocale);
return (
<IntlProvider
locale={locale}
messages={messages[locale]}
defaultLocale={defaultLocale}
>
<div className="App">
<HomePage locale={locale} setLocale={setLocale} />
</div>
</IntlProvider>
);
}
export default App;
O con TypeScript (src/App.tsx):
import React, { useState } from 'react';
import { IntlProvider } from 'react-intl';
import { messages, defaultLocale, locales } from './locales';
import HomePage from './components/HomePage';
const App: React.FC = () => {
const [locale, setLocale] = useState<string>(defaultLocale);
return (
<IntlProvider
locale={locale}
messages={messages[locale as keyof typeof messages]}
defaultLocale={defaultLocale}
>
<div className="App">
<HomePage locale={locale} setLocale={setLocale} />
</div>
</IntlProvider>
);
};
export default App;
Paso 5: Usar Componente FormattedMessage
Usa el componente FormattedMessage para traducciones:
Antes:
function Welcome() {
return <h1>Welcome to our app!</h1>;
}
Después:
import { FormattedMessage } from 'react-intl';
function Welcome() {
return (
<h1>
<FormattedMessage id="app.welcome" />
</h1>
);
}
Paso 6: Usar Hook useIntl
Para más flexibilidad, usa el hook useIntl:
import { useIntl } from 'react-intl';
function Welcome() {
const intl = useIntl();
return <h1>{intl.formatMessage({ id: 'app.welcome' })}</h1>;
}
Con TypeScript:
import { useIntl } from 'react-intl';
const Welcome: React.FC = () => {
const intl = useIntl();
return <h1>{intl.formatMessage({ id: 'app.welcome' })}</h1>;
};
Paso 7: Interpolación de Cadenas con Variables
Pasa variables a las traducciones usando formato de mensaje ICU:
translation.json:
{
"user.greeting": "Hello, {name}!"
}
Componente:
import { FormattedMessage } from 'react-intl';
function UserGreeting({ name }) {
return (
<p>
<FormattedMessage
id="user.greeting"
values={{ name: name }}
/>
</p>
);
}
// Uso: <UserGreeting name="John" />
// Inglés: "Hello, John!"
// Español: "¡Hola, John!"
O con useIntl:
import { useIntl } from 'react-intl';
function UserGreeting({ name }) {
const intl = useIntl();
return (
<p>
{intl.formatMessage(
{ id: 'user.greeting' },
{ name: name }
)}
</p>
);
}
Paso 8: Pluralización
react-intl usa formato de mensaje ICU para pluralización:
translation.json:
{
"items.count": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
Componente:
import { FormattedMessage } from 'react-intl';
function ItemCount({ count }) {
return (
<p>
<FormattedMessage
id="items.count"
values={{ count: count }}
/>
</p>
);
}
// Ejemplos de uso:
// <ItemCount count={0} /> → "No items" (Inglés) / "No hay elementos" (Español)
// <ItemCount count={1} /> → "1 item" (Inglés) / "1 elemento" (Español)
// <ItemCount count={5} /> → "5 items" (Inglés) / "5 elementos" (Español)
Reglas de Plural ICU:
=0- Exactamente ceroone- Singular (1)other- Plural (2, 3, 4, etc.)#- Abreviación para el valor numérico
Paso 9: Formatear Fechas
Usa el componente FormattedDate o la API formatDate:
Enfoque con componente:
import { FormattedDate } from 'react-intl';
function DateDisplay({ date }) {
return (
<p>
<FormattedDate
value={date}
year="numeric"
month="long"
day="numeric"
weekday="long"
/>
</p>
);
}
// Inglés: "Wednesday, March 20, 2025"
// Español: "miércoles, 20 de marzo de 2025"
Enfoque con hook:
import { useIntl } from 'react-intl';
function DateDisplay({ date }) {
const intl = useIntl();
const formattedDate = intl.formatDate(date, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
});
return <p>{formattedDate}</p>;
}
Con formato de mensaje ICU:
{
"date.today": "Today is {date, date, full}"
}
import { FormattedMessage, FormattedDate } from 'react-intl';
function TodayDate() {
const today = new Date();
return (
<p>
<FormattedMessage
id="date.today"
values={{ date: today }}
/>
</p>
);
}
Paso 10: Formatear Números
Usa el componente FormattedNumber o la API formatNumber:
Enfoque con componente:
import { FormattedNumber } from 'react-intl';
function NumberDisplay({ number }) {
return (
<p>
<FormattedNumber value={number} />
</p>
);
}
// Inglés: "1,234.56"
// Español: "1.234,56"
Enfoque con hook:
import { useIntl } from 'react-intl';
function NumberDisplay({ number }) {
const intl = useIntl();
const formattedNumber = intl.formatNumber(number);
return <p>{formattedNumber}</p>;
}
Formateo de moneda:
import { FormattedNumber } from 'react-intl';
function PriceDisplay({ amount }) {
return (
<p>
<FormattedNumber
value={amount}
style="currency"
currency="USD"
/>
</p>
);
}
// Inglés (US): "$1,234.56"
// Español (ES): "1.234,56 €"
Con formato de mensaje ICU:
{
"currency.price": "Price: {amount, number, currency}"
}
import { FormattedMessage } from 'react-intl';
function Price({ amount }) {
return (
<p>
<FormattedMessage
id="currency.price"
values={{ amount: amount }}
/>
</p>
);
}
Paso 11: Formatear Tiempo Relativo
Usa FormattedRelativeTime para fechas relativas:
import { FormattedRelativeTime } from 'react-intl';
function RelativeTime({ date }) {
const now = new Date();
const diffInSeconds = Math.floor((date - now) / 1000);
return (
<p>
<FormattedRelativeTime
value={diffInSeconds}
numeric="auto"
updateIntervalInSeconds={60}
/>
</p>
);
}
// Ejemplos: "en 2 horas", "hace 2 horas", "ayer"
Paso 12: Cambiar Idioma Programáticamente
Permite a los usuarios cambiar idiomas:
import { useIntl } from 'react-intl';
function LanguageSwitcher({ locale, setLocale }) {
const intl = useIntl();
const changeLanguage = (newLocale) => {
setLocale(newLocale);
// Opcionalmente guardar en localStorage
localStorage.setItem('language', newLocale);
};
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('es')}>Español</button>
<button onClick={() => changeLanguage('fr')}>Français</button>
</div>
);
}
Con detección de idioma:
import { useState, useEffect } from 'react';
function App() {
const [locale, setLocale] = useState(() => {
// Intentar obtener idioma guardado de localStorage
const saved = localStorage.getItem('language');
if (saved) return saved;
// Detectar idioma del navegador
const browserLang = navigator.language.split('-')[0];
return ['en', 'es', 'fr'].includes(browserLang) ? browserLang : 'en';
});
useEffect(() => {
localStorage.setItem('language', locale);
}, [locale]);
return (
<IntlProvider locale={locale} messages={messages[locale]}>
{/* Tu app */}
</IntlProvider>
);
}
Paso 13: Manejar Idiomas de Derecha a Izquierda (RTL)
Para idiomas RTL como árabe y hebreo:
import { useEffect } from 'react';
import { useIntl } from 'react-intl';
function App() {
const intl = useIntl();
const locale = intl.locale;
useEffect(() => {
const isRTL = ['ar', 'he', 'fa'].includes(locale);
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
document.documentElement.lang = locale;
}, [locale]);
return (
<div className="App">
{/* Contenido de tu app */}
</div>
);
}
Mejores Prácticas
1. Usar IDs de Mensaje Descriptivos
Malo:
{
"msg1": "Submit"
}
Bueno:
{
"button.submit": "Submit"
}
2. Organizar Mensajes por Característica
translation.json:
{
"auth": {
"login": "Login",
"logout": "Logout",
"signup": "Sign Up"
},
"dashboard": {
"title": "Dashboard",
"welcome": "Welcome back!"
}
}
3. Usar Formato de Mensaje ICU
Siempre usa formato ICU para mensajes complejos:
{
"user.status": "{name} has {count, plural, =0 {no tasks} one {# task} other {# tasks}}"
}
4. Proporcionar Mensajes por Defecto
Usa la prop defaultMessage para desarrollo:
<FormattedMessage
id="app.welcome"
defaultMessage="Welcome to our app!"
/>
5. Extraer Mensajes para Traducción
Usa @formatjs/cli para extraer mensajes:
npm install --save-dev @formatjs/cli
# Extraer mensajes
formatjs extract "src/**/*.{js,jsx,ts,tsx}" --out-file locales/en.json
6. Probar Longitudes de Cadenas
Algunos idiomas son más largos que otros. Diseña tu UI para acomodar:
- Alemán y finlandés: 30-50% más largo que inglés
- Idiomas asiáticos: Pueden necesitar más espacio vertical
Errores Comunes
1. Olvidar Envolver App con IntlProvider
Siempre envuelve tu app con IntlProvider:
// ❌ No funcionará
function App() {
return <Welcome />;
}
// ✅ Correcto
function App() {
return (
<IntlProvider locale="en" messages={messages.en}>
<Welcome />
</IntlProvider>
);
}
2. No Proporcionar Mensajes para Locale
Asegúrate de que los mensajes existan para el locale seleccionado:
// ❌ Mostrará IDs de mensaje si messages.es no existe
<IntlProvider locale="es" messages={messages.en}>
// ✅ Correcto
<IntlProvider locale="es" messages={messages.es}>
3. Codificar Cadenas de Formato Directamente
Malo:
const price = `$${amount.toFixed(2)}`;
Bueno:
<FormattedNumber
value={amount}
style="currency"
currency="USD"
/>
4. No Manejar Mensajes Faltantes
Proporciona fallbacks:
<FormattedMessage
id="app.welcome"
defaultMessage="Welcome"
/>
Avanzado: Formateo de Texto Enriquecido
Usa FormattedMessage con texto enriquecido:
{
"welcome": "Welcome to <bold>our app</bold>!"
}
import { FormattedMessage } from 'react-intl';
function Welcome() {
return (
<FormattedMessage
id="app.welcome"
values={{
bold: (chunks) => <strong>{chunks}</strong>
}}
/>
);
}
Avanzado: Descripciones de Mensajes
Agrega descripciones para ayudar a los traductores:
{
"button.delete": "Delete",
"@button.delete": {
"description": "Button to delete an item. Shown in the item detail view."
}
}
Avanzado: Soporte TypeScript
Para mejor soporte TypeScript:
src/types/react-intl.d.ts:
import { Messages } from '@formatjs/intl';
declare module 'react-intl' {
interface IntlMessages extends Messages {
'app.welcome': string;
'button.submit': string;
// ... otras claves de mensaje
}
}
Avanzado: Carga Perezosa de Mensajes
Carga traducciones bajo demanda:
import { useState, useEffect } from 'react';
import { IntlProvider } from 'react-intl';
function App() {
const [locale, setLocale] = useState('en');
const [messages, setMessages] = useState({});
useEffect(() => {
import(`./locales/${locale}.json`)
.then((module) => setMessages(module.default))
.catch(() => setMessages({}));
}, [locale]);
return (
<IntlProvider locale={locale} messages={messages}>
{/* Tu app */}
</IntlProvider>
);
}
Usar con Next.js
Para aplicaciones Next.js, usa next-intl o configura manualmente:
pages/_app.js:
import { IntlProvider } from 'react-intl';
import { useRouter } from 'next/router';
import { messages } from '../locales';
export default function App({ Component, pageProps }) {
const { locale } = useRouter();
return (
<IntlProvider
locale={locale}
messages={messages[locale]}
>
<Component {...pageProps} />
</IntlProvider>
);
}
Conclusión
Localizar tu app React con react-intl es sencillo cuando sigues estos pasos:
- Instalar paquete
react-intl - Crear archivos JSON de traducción para cada idioma usando formato de mensaje ICU
- Cargar traducciones y envolver tu app con
IntlProvider - Usar componente
FormattedMessageo hookuseIntl - Manejar pluralización con formato ICU
- Formatear fechas, números y moneda usando componentes integrados
- Permitir a los usuarios cambiar idiomas
- Probar exhaustivamente en todos los idiomas soportados
Siguiendo estas prácticas, crearás una app que proporciona una experiencia nativa para usuarios en todo el mundo, expandiendo significativamente tu base de usuarios potencial.
Optimiza tu Flujo de Trabajo de Localización React
Gestionar traducciones para múltiples idiomas puede volverse complejo a medida que tu app crece. Considera usar una plataforma de gestión de traducciones para:
- Colaborar con traductores
- Mantener traducciones sincronizadas con tu código base
- Automatizar el flujo de trabajo de traducción
- Mantener consistencia en todos los idiomas
- Generar archivos de traducción automáticamente
¿Listo para llevar tu app React a nivel global? Explora la plataforma de localización de AZbox y optimiza tu flujo de trabajo de traducción: