Construyendo una Plataforma de Calculadoras Multilingüe con Next.js 15
Análisis detallado de cómo construir una plataforma de calculadoras multilingüe de alto rendimiento con Next.js 15, React 19 y next-intl, incluyendo implementación de internacionalización, optimización SEO y rendimiento.
¡Hola, compañeros desarrolladores! 👋
Si alguna vez has intentado construir un sitio web que necesita funcionar para usuarios de todo el mundo, sabes lo desafiante que puede ser. Recientemente emprendí un viaje para crear Free Calculators - una plataforma que sirve a usuarios en 12 idiomas diferentes, y déjame decirte, ¡fue toda una aventura!
En esta publicación, quiero compartir la historia real detrás de la construcción de esta plataforma de calculadoras multilingüe. No solo la versión "esto es lo que hicimos", sino la versión "esto es lo que realmente pasó, qué salió mal, y qué aprendimos". Porque honestamente, construir para una audiencia global no es un paseo por el parque.
Por Qué Construimos Esto (Y Por Qué Importa)
Imagina esto: Estás construyendo una aplicación de calculadora. Bastante sencillo, ¿verdad? Pero entonces te das cuenta de que alguien en Japón podría necesitarla en japonés, alguien en Brasil podría preferir portugués, y tus usuarios alemanes definitivamente quieren que sus números estén formateados a la manera alemana (1.234,56 en lugar de 1,234.56). De repente, tu simple calculadora se convierte en un proyecto internacional complejo.
Eso es exactamente lo que nos pasó. Comenzamos con una calculadora básica y terminamos construyendo una plataforma que sirve a usuarios de diferentes culturas, idiomas y formatos de números. ¿Y sabes qué? Se ha convertido en uno de los proyectos más gratificantes en los que he trabajado.
Decisiones del Stack Tecnológico: Por Qué Elegimos Estas Herramientas
¿Por Qué Next.js 15?
Para ser honesto, cuando Next.js 15 salió por primera vez, estábamos un poco indecisos. Después de todo, "si no está roto, no lo arregles", ¿verdad? Pero cuando vimos el nuevo App Router y la integración de React 19, decidimos dar el salto. Resultó ser una elección sabia.
// Así es como configuramos nuestro enrutamiento
export const routing = defineRouting({
locales: [
'en',
'zh',
'ja',
'ko',
'fr',
'de',
'es',
'pt',
'ru',
'ar',
'hi',
'it',
],
defaultLocale: 'en',
localePrefix: 'as-needed', // Esta configuración nos ahorró muchos dolores de cabeza
localeDetection: true,
});
React 19: ¿Vale la Pena la Actualización?
Respuesta corta: ¡Absolutamente! Respuesta larga: Nos encontramos con algunos problemas de compatibilidad, pero las mejoras de rendimiento de las nuevas características son significativas. Especialmente las mejoras a Suspense y las características concurrentes hicieron nuestra experiencia de usuario mucho mejor.
TypeScript: Nuestro Salvador
No puedo imaginar gestionar archivos de traducción para 12 idiomas sin TypeScript. La seguridad de tipos nos dio confianza al refactorizar, especialmente al manejar objetos de traducción anidados complejos.
// Así es como definimos nuestros tipos de traducción
interface TranslationKeys {
nav: {
siteName: string;
home: string;
calculators: string;
};
common: {
calculate: string;
clear: string;
result: string;
};
}
Implementación de Internacionalización: Desafíos Reales y Soluciones
Desafío 1: La Complejidad de la Detección de Idioma
Podrías pensar, "¿Detección de idioma? ¡Fácil!" Pero no lo es. Los usuarios podrían acceder desde diferentes dispositivos, usar VPNs, o tener preferencias de idioma específicas. Pasamos mucho tiempo perfeccionando nuestra lógica de detección de idioma.
// Nuestra configuración de middleware
import createMiddleware from 'next-intl/middleware';
import { routing } from '@/i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
Desafío 2: Gestión de Archivos de Traducción
Gestionar archivos de traducción para 12 idiomas es como gestionar 12 proyectos diferentes. Desarrollamos algunos scripts de automatización para ayudarnos:
// Nuestro script de gestión de traducciones
const addMissingTranslations = async () => {
const baseTranslations = await loadTranslations('en');
const allLocales = await getAllLocales();
for (const locale of allLocales) {
const existingTranslations = await loadTranslations(locale);
const missingKeys = findMissingKeys(baseTranslations, existingTranslations);
if (missingKeys.length > 0) {
console.log(`Missing keys in ${locale}:`, missingKeys);
await addMissingKeys(locale, missingKeys);
}
}
};
Desafío 3: La Trampa del Formateo de Números
Este fue uno de los desafíos más interesantes. ¿Sabías que diferentes países tienen diferentes formatos de números? Alemania usa comas como separadores decimales, Estados Unidos usa puntos. Nos encontramos con muchos problemas al manejar cálculos de moneda hasta que implementamos formateo localizado apropiado.
Optimización SEO: Haciendo que los Motores de Búsqueda Nos Amen
El Poder de la Generación de Sitio Estático
Decidimos usar Generación de Sitio Estático (SSG), lo que podría ser la mejor decisión que jamás hayamos tomado. No solo las páginas son súper rápidas, sino que los resultados de SEO también son excelentes.
// Generar páginas estáticas para cada idioma
export const dynamic = 'force-static';
export function generateStaticParams() {
return routing.locales
.filter(locale => locale !== routing.defaultLocale)
.map(locale => ({ locale }));
}
El Arte de la Generación de Metadatos
Generar metadatos correctos para cada idioma requiere algo de destreza. Desarrollamos un sistema para generar automáticamente metadatos amigables para SEO:
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Home' });
return {
title: t('title'),
description: t('description'),
openGraph: {
title: t('title'),
description: t('description'),
locale: locale,
},
};
}
Optimización de Rendimiento: Haciendo la Experiencia de Usuario Fluida
La Importancia de la Optimización de Imágenes
Pasamos mucho tiempo optimizando la carga de imágenes. La adopción de formatos AVIF y WebP mejoró nuestras velocidades de carga de página en un 40%.
// Configuración de imágenes en next.config.ts
images: {
formats: ['image/avif', 'image/webp'],
domains: ['freecalculators.app'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
}
Estrategia de Caché: Nuestro Arma Secreta
Implementar la estrategia de caché correcta requirió algunas pruebas y errores. Terminamos con un enfoque de caché en capas:
async headers() {
return [
// Caché fuerte para recursos estáticos
{
source: '/(.*).(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)',
headers: [{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
}],
},
// Caché negociado para páginas
{
source: '/(.*)',
headers: [{
key: 'Cache-Control',
value: 'public, max-age=0, must-revalidate',
}],
},
];
}
Diseño de Experiencia de Usuario: Haciendo que Todos se Sientan Bienvenidos
El Desafío del Diseño Responsivo
Asegurar que nuestras calculadoras funcionen perfectamente en todos los dispositivos es un desafío continuo. Adoptamos un enfoque mobile-first:
// Layout responsivo mobile-first
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-1/2">
{/* Interfaz de calculadora */}
</div>
<div className="w-full md:w-1/2">
{/* Visualización de resultados */}
</div>
</div>
Accesibilidad: Más que Solo Cumplimiento
Nos dimos cuenta de que la accesibilidad no es solo un requisito legal - es lo correcto. Agregamos etiquetas ARIA apropiadas a cada elemento interactivo:
<button
aria-label={t('common.calculate')}
aria-describedby="calculator-help"
className="bg-blue-600 text-white px-4 py-2 rounded"
>
{t('common.calculate')}
</button>
Cadena de Herramientas de Desarrollo: Haciendo el Desarrollo Más Eficiente
Monitoreo de Errores: Nuestra Red de Seguridad
Integramos Sentry para el monitoreo de errores, lo que nos ayudó a descubrir muchos problemas que nunca anticipamos:
export default withSentryConfig(withNextIntl(nextConfig), {
org: 'my-projects-pd',
project: 'free-calculators',
widenClientFileUpload: true,
disableLogger: true,
automaticVercelMonitors: true,
});
Gestión Automatizada de Traducciones
Desarrollamos algunos scripts para gestionar archivos de traducción, y estos scripts son ahora una parte indispensable de nuestro flujo de trabajo.
Lecciones que Aprendimos
1. Piensa en la Internacionalización Desde el Principio
Si crees que podrías necesitar soporte multilingüe, diseñalo desde el principio. Agregar internacionalización más tarde es mucho más difícil que diseñarla desde el principio.
2. Experiencia de Usuario Sobre Exhibicionismo Técnico
Pasamos mucho tiempo optimizando el rendimiento, pero al final a los usuarios les importa más si la interfaz es intuitiva y los cálculos son precisos.
3. La Automatización es Clave
Gestionar manualmente las traducciones para 12 idiomas no es realista. Los scripts de automatización son el salvavidas de nuestro proyecto.
4. Prueba, Prueba y Prueba de Nuevo
Diferentes idiomas tienen diferentes longitudes de texto, diferentes formatos de números y diferentes expectativas culturales. Las pruebas exhaustivas son imprescindibles.
Planes Futuros
Estamos considerando agregar más idiomas, mejorar nuestro proceso de gestión de traducciones y explorar posibilidades de traducción asistida por IA. Pero lo más importante, queremos continuar proporcionando a los usuarios la mejor experiencia de calculadora.
Conclusión
Construir una aplicación web multilingüe es un desafío, pero también es una experiencia muy gratificante. A través de elecciones tecnológicas inteligentes y muchas pruebas, creamos exitosamente una plataforma que realmente sirve a usuarios globales.
Si estás considerando construir una aplicación multilingüe, espero que este artículo te dé algo de inspiración. Recuerda, cada desafío es una oportunidad de aprendizaje, y cada error es un trampolín hacia el éxito.
Enlace del Proyecto: https://www.freecalculators.app Stack Tecnológico: Next.js 15, React 19, TypeScript, Tailwind CSS, next-intl