Una de las cosas (y son muchas) que PHPRunner tiene muy bien resuelta es la construcción de aplicaciones multi-idioma.
Este artículo es la búsqueda de una de las soluciones que dispone React de producir aplicaciones multi-idiomas. He utilizado para el ejemplo la solución de i18next y más concretamente el componente react-i18next, que es una solución muy sencilla y elegante, por lo que recomiendo su utilización.
Objetivo
Poder desarrollar aplicaciones multi-idioma, los que se deseen, detectando el idioma por el navegador o sistema operativo, en donde funcione la aplicación.
DEMO: https://fhumanes.com/i18n-react/
Solución Técnica
El ejemplo lo he realizado con la IA DeepSeek y tiene por objetivo comprobar cómo los diferentes atributos de una pantalla pueden estar en 2 idiomas (inglés y español).
En el ejemplo, se accede al idioma del navegador y lo presenta en ese idioma, ahora bien, a través de los botones «Inglés» y «Español», se cambia el idioma de la página.
Como he explicado he utilizado el componente react-i18next, dispone de buena documentación y una vez comprendido el funcionamiento, resulta bastante sencillo de utilizar y tiene mucha potencia de configuración para la impresión de fechas e importes.
El ejemplo tiene esta estructura de directorios y ficheros:
(1) Este directorio contiene el fichero «config.js» (2) que controla el idioma que está definido por defecto y los directorios y ficheros de traducción de la etiquetas que se usan y su traducción (3) en cada uno de los idiomas.
(4) El directorio «components», serían las páginas de nuestra aplicación, en este caso un fichero que simula la cabeceras o menús de las páginas y otro que simula el contenido de una página.
En estas páginas, que son las que muestra el ejemplo, podremos apreciar el uso de las etiquetas que se traducen al idioma configurado.
Voy a poner los ficheros que considero más importantes.
App.js
import React from 'react'; import './i18n/config'; import { useTranslation } from 'react-i18next'; import Header from './components/Header'; import UserDashboard from './components/UserDashboard'; const App = () => { const { t } = useTranslation(); const user = { name: 'Carlos', lastLogin: new Date('2023-05-15') }; return ( <div className="app"> <Header /> <main> <UserDashboard user={user} /> {/* Ejemplo de validación */} <div className="form-error"> <p>{t('errors.required')}</p> <p>{t('errors.email')}</p> </div> </main> </div> ); }; export default App;
config.js Las traducciones las he puesto locales (front-end), pero podían estar en el server (back-end).
import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; // Importar formatos adicionales import { format, toDate } from 'date-fns'; import { enUS, es } from 'date-fns/locale'; // Importaciones como módulos ES import enCommon from './en/common.json'; import esCommon from './es/common.json'; const dateFormats = { short: 'MM/dd/yyyy', long: 'MMMM dd, yyyy' }; i18n .use(Backend) .use(LanguageDetector) .use(initReactI18next) .init({ fallbackLng: 'en', debug: false, // Habilita temporalmente para ver mensajes de consola interpolation: { escapeValue: false, format: (value, formatType, lng) => { if (value instanceof Date) { const locale = lng === 'es' ? es : enUS; return format(value, formatType, { locale }); } if (formatType === 'currency') { return new Intl.NumberFormat(lng, { style: 'currency', useGrouping: true, minimumFractionDigits: 2, currency: lng === 'es' ? 'EUR' : 'USD' }).format(value); } return value; } }, /* backend: { loadPath: '/i18n-react/locales/{{lng}}/{{ns}}.json', // Ajustado manualmente }, */ // Para desarrollo, puedes cargar las traducciones directamente: resources: { en: { translation: { ...enCommon, dateFormats } }, es: { translation: { ...esCommon, dateFormats } } } }); export default i18n;
common.json
{ "header": { "title": "Bienvenido a Mi Aplicación", "subtitle": "El mejor lugar para tus necesidades" }, "dashboard": { "welcome": "¡Hola, {{name}}!", "messages": { "zero": "No tienes mensajes nuevos", "one": "Tienes un mensaje nuevo", "other": "Tienes {{count}} mensajes nuevos" }, "lastLogin": "Último acceso: {{date, dd/MM/yyyy}}", "balance": "Tu saldo es {{value, currency}}", "currency": "EUR", "notifications": { "title": "Notificaciones", "empty": "No hay notificaciones" } }, "buttons": { "login": "Iniciar Sesión", "logout": "Cerrar Sesión", "changeLanguage": "Cambiar Idioma" }, "errors": { "required": "Este campo es obligatorio", "email": "Por favor ingresa un email válido" } }
Header.js
import React from 'react'; import { useTranslation } from 'react-i18next'; const Header = () => { const { t } = useTranslation(); return ( <header> <h1>{t('header.title')}</h1> <p>{t('header.subtitle')}</p> </header> ); }; export default Header;
UserDashboard.js
import React from 'react'; import { useTranslation } from 'react-i18next'; // utils/language.js //Para obtener el idiona actual import i18n from '../i18n/config'; const UserDashboard = ({ user }) => { const { t, i18n } = useTranslation(); const changeLanguage = (lng) => { i18n.changeLanguage(lng); }; const lastLogin = new Date(user.lastLogin); const balance = 1250.5; const messageCount = 3; // Obtener el idioma actual de la aplicación const currentLanguage = i18n.language; console.log('El idioma actual es:', currentLanguage); const idiomas = Object.keys(i18n.services.resourceStore.data); console.log('Los idiomas son: ', idiomas); const languageReal = i18n.resolvedLanguage; console.log('El idioma real es: ', languageReal); return ( <div className="dashboard"> <h2>{t('dashboard.welcome', { name: user.name })}</h2> <p>{t('dashboard.messages', { count: messageCount })}</p> <p>{t('dashboard.lastLogin', { date: lastLogin })}</p> <p>{t('dashboard.balance', { value: balance })}</p> <div className="notifications"> <h3>{t('dashboard.notifications.title')}</h3> <p>{t('dashboard.notifications.empty')}</p> </div> <div className="language-selector"> <button onClick={() => changeLanguage('en')}>English</button> <button onClick={() => changeLanguage('es')}>Español</button> </div> <button>{t('buttons.logout')}</button> </div> ); }; export default UserDashboard;
Espero que os sea útil y os guste y como siempre, os dejo los fuentes del ejemplo para que podáis descargarlos y probad en vuestros PC’s.