Construindo uma Plataforma de Calculadoras Multilíngue com Next.js 15
Análise detalhada de como construir uma plataforma de calculadoras multilíngue de alto desempenho com Next.js 15, React 19 e next-intl, incluindo implementação de internacionalização, otimização SEO e desempenho.
Olá, colegas desenvolvedores! 👋
Se você já tentou construir um site que precisa funcionar para usuários ao redor do mundo, você sabe o quão desafiador pode ser. Recentemente embarquei em uma jornada para criar Free Calculators - uma plataforma que serve usuários em 12 idiomas diferentes, e deixe-me dizer, foi uma verdadeira aventura!
Neste post, quero compartilhar a história real por trás da construção desta plataforma de calculadoras multilíngue. Não apenas a versão "aqui está o que fizemos", mas a versão "aqui está o que realmente aconteceu, o que deu errado, e o que aprendemos". Porque honestamente, construir para uma audiência global não é um passeio no parque.
Por Que Construímos Isso (E Por Que Importa)
Imagine isso: Você está construindo um app de calculadora. Bem direto, certo? Mas então você percebe que alguém no Japão pode precisar dele em japonês, alguém no Brasil pode preferir português, e seus usuários alemães definitivamente querem seus números formatados da forma alemã (1.234,56 em vez de 1,234.56). De repente, sua calculadora simples se torna um projeto internacional complexo.
Isso é exatamente o que aconteceu conosco. Começamos com uma calculadora básica e acabamos construindo uma plataforma que serve usuários de diferentes culturas, idiomas e formatos de números. E sabe o quê? Tornou-se um dos projetos mais recompensadores em que já trabalhei.
Decisões de Stack Tecnológico: Por Que Escolhemos Essas Ferramentas
Por Que Next.js 15?
Para ser honesto, quando Next.js 15 saiu pela primeira vez, estávamos um pouco hesitantes. Afinal, "se não está quebrado, não conserte", certo? Mas quando vimos o novo App Router e a integração React 19, decidimos dar o salto. Acabou sendo uma escolha sábia.
// É assim que configuramos nosso roteamento
export const routing = defineRouting({
locales: [
'en',
'zh',
'ja',
'ko',
'fr',
'de',
'es',
'pt',
'ru',
'ar',
'hi',
'it',
],
defaultLocale: 'en',
localePrefix: 'as-needed', // Esta configuração nos poupou muitas dores de cabeça
localeDetection: true,
});
React 19: Vale a Pena Atualizar?
Resposta curta: Absolutamente! Resposta longa: Encontramos alguns problemas de compatibilidade, mas as melhorias de performance das novas funcionalidades são significativas. Especialmente as melhorias no Suspense e funcionalidades concorrentes tornaram nossa experiência do usuário muito melhor.
TypeScript: Nosso Salvador
Não consigo imaginar gerenciar arquivos de tradução para 12 idiomas sem TypeScript. A segurança de tipos nos deu confiança ao refatorar, especialmente ao lidar com objetos de tradução aninhados complexos.
// É assim que definimos nossos tipos de tradução
interface TranslationKeys {
nav: {
siteName: string;
home: string;
calculators: string;
};
common: {
calculate: string;
clear: string;
result: string;
};
}
Implementação de Internacionalização: Desafios Reais e Soluções
Desafio 1: A Complexidade da Detecção de Idioma
Você pode pensar, "Detecção de idioma? Fácil!" Mas não é. Usuários podem acessar de diferentes dispositivos, usar VPNs, ou ter preferências de idioma específicas. Passamos muito tempo aperfeiçoando nossa lógica de detecção de idioma.
// Nossa configuração 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|.*\\..*).*)',
};
Desafio 2: Gerenciamento de Arquivos de Tradução
Gerenciar arquivos de tradução para 12 idiomas é como gerenciar 12 projetos diferentes. Desenvolvemos alguns scripts de automação para nos ajudar:
// Nosso script de gerenciamento de tradução
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);
}
}
};
Desafio 3: A Armadilha da Formatação de Números
Este foi um dos desafios mais interessantes. Você sabia que diferentes países têm diferentes formatos de números? A Alemanha usa vírgulas como separadores decimais, os EUA usam pontos. Encontramos muitos problemas ao lidar com cálculos de moeda até implementarmos formatação localizada apropriada.
Otimização SEO: Fazendo os Mecanismos de Busca Nos Amarem
O Poder da Geração de Site Estático
Decidimos usar Geração de Site Estático (SSG), o que pode ser a melhor decisão que já tomamos. Não apenas as páginas são super rápidas, mas os resultados de SEO também são excelentes.
// Gerar 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 }));
}
A Arte da Geração de Metadados
Gerar metadados corretos para cada idioma requer alguma sutileza. Desenvolvemos um sistema para gerar automaticamente metadados amigáveis ao 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,
},
};
}
Otimização de Performance: Tornando a Experiência do Usuário Fluida
A Importância da Otimização de Imagens
Passamos muito tempo otimizando o carregamento de imagens. A adoção dos formatos AVIF e WebP melhorou nossas velocidades de carregamento de página em 40%.
// Configuração de imagens em 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],
}
Estratégia de Cache: Nossa Arma Secreta
Implementar a estratégia de cache correta exigiu algumas tentativas e erros. Acabamos com uma abordagem de cache em camadas:
async headers() {
return [
// Cache forte 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',
}],
},
// Cache negociado para páginas
{
source: '/(.*)',
headers: [{
key: 'Cache-Control',
value: 'public, max-age=0, must-revalidate',
}],
},
];
}
Design de Experiência do Usuário: Fazendo Todos Se Sentirem Bem-vindos
O Desafio do Design Responsivo
Garantir que nossas calculadoras funcionem perfeitamente em todos os dispositivos é um desafio contínuo. Adotamos uma abordagem mobile-first:
// Layout responsivo mobile-first
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-1/2">
{/* Interface da calculadora */}
</div>
<div className="w-full md:w-1/2">
{/* Exibição de resultados */}
</div>
</div>
Acessibilidade: Mais do que Apenas Conformidade
Percebemos que acessibilidade não é apenas um requisito legal - é a coisa certa a fazer. Adicionamos rótulos ARIA apropriados a cada elemento interativo:
<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>
Cadeia de Ferramentas de Desenvolvimento: Tornando o Desenvolvimento Mais Eficiente
Monitoramento de Erros: Nossa Rede de Segurança
Integramos Sentry para monitoramento de erros, o que nos ajudou a descobrir muitos problemas que nunca antecipamos:
export default withSentryConfig(withNextIntl(nextConfig), {
org: 'my-projects-pd',
project: 'free-calculators',
widenClientFileUpload: true,
disableLogger: true,
automaticVercelMonitors: true,
});
Gerenciamento Automatizado de Tradução
Desenvolvemos alguns scripts para gerenciar arquivos de tradução, e esses scripts são agora uma parte indispensável do nosso fluxo de trabalho.
Lições que Aprendemos
1. Pense em Internacionalização Desde o Início
Se você acha que pode precisar de suporte multilíngue, projete isso desde o início. Adicionar internacionalização mais tarde é muito mais difícil do que projetá-la desde o início.
2. Experiência do Usuário Sobre Exibicionismo Técnico
Passamos muito tempo otimizando performance, mas no final os usuários se importam mais se a interface é intuitiva e os cálculos são precisos.
3. Automação é a Chave
Gerenciar manualmente traduções para 12 idiomas não é realista. Scripts de automação são a corda de salvamento do nosso projeto.
4. Teste, Teste e Teste Novamente
Diferentes idiomas têm diferentes comprimentos de texto, diferentes formatos de números e diferentes expectativas culturais. Testes abrangentes são obrigatórios.
Planos Futuros
Estamos considerando adicionar mais idiomas, melhorar nosso processo de gerenciamento de tradução e explorar possibilidades de tradução assistida por IA. Mas o mais importante, queremos continuar fornecendo aos usuários a melhor experiência de calculadora.
Conclusão
Construir uma aplicação web multilíngue é um desafio, mas também é uma experiência muito recompensadora. Através de escolhas tecnológicas inteligentes e muitos testes, criamos com sucesso uma plataforma que realmente serve usuários globais.
Se você está considerando construir uma aplicação multilíngue, espero que este artigo lhe dê alguma inspiração. Lembre-se, cada desafio é uma oportunidade de aprendizado, e cada erro é um trampolim para o sucesso.
Link do Projeto: https://www.freecalculators.app Stack Tecnológico: Next.js 15, React 19, TypeScript, Tailwind CSS, next-intl