Building a Multilingual Calculator Platform: A Developer's Real Story

Deep dive into building a high-performance multilingual calculator platform with Next.js 15, React 19, and next-intl, including internationalization implementation, SEO optimization, and performance strategies.

7 min read
Next.jsReactInternationalizationSEOPerformanceTypeScript

Hey there, fellow developers! ๐Ÿ‘‹

If you've ever tried to build a website that needs to serve users around the world, you know just how challenging this can be. I recently embarked on creating Free Calculators โ€” a platform serving users in 12 different languages, and let me tell you, it's been quite an adventure!

In this article, I want to share the real story behind building this multilingual calculator platform. Not just the "here's what we did" version, but the "here's what actually happened, what went wrong, and what we learned" version. Because honestly, building applications for a global audience is no walk in the park.

Why We Built This (And Why It Matters)

Imagine this: You're building a calculator app. Simple enough, right? But then you realize that someone in Japan might need a Japanese version, someone in Brazil might prefer Portuguese, and your German users absolutely want numbers formatted the German way (1.234,56 instead of 1,234.56). Suddenly, your simple calculator becomes a complex international project.

This is exactly what happened to us. We started with basic calculators and ended up building a platform that serves users from different cultures, languages, and number formatting preferences. You know what? It's been one of the most rewarding projects I've ever worked on.

Tech Stack Choices: Why We Picked These Tools

Why Next.js 15?

To be honest, when Next.js 15 was first released, we were a bit hesitant. After all, "if it ain't broke, don't fix it," right? But when we saw the new App Router and React 19 integration, we decided to take the leap. It turned out to be a wise choice.

// This is how we set up our routing
export const routing = defineRouting({
  locales: [
    'en',
    'zh',
    'ja',
    'ko',
    'fr',
    'de',
    'es',
    'pt',
    'ru',
    'ar',
    'hi',
    'it',
  ],
  defaultLocale: 'en',
  localePrefix: 'as-needed', // This setting saved us a lot of headaches
  localeDetection: true,
});

React 19: Was It Worth the Upgrade?

Short answer: Absolutely! Long answer: We ran into some compatibility issues, but the performance improvements from new features are significant. Especially the improvements to Suspense and concurrent features gave our user experience a qualitative boost.

TypeScript: Our Lifesaver

I can't imagine managing translation files for 12 languages without TypeScript. Type safety gave us confidence during refactoring, especially when dealing with complex nested translation objects.

// This is how we define our translation types
interface TranslationKeys {
  nav: {
    siteName: string;
    home: string;
    calculators: string;
  };
  common: {
    calculate: string;
    clear: string;
    result: string;
  };
}

Internationalization Implementation: Real Challenges and Solutions

Challenge 1: The Complexity of Language Detection

You might think, "Language detection? Easy!" But it's actually not. Users might access from different devices, might use VPNs, might prefer specific language settings. We spent a lot of time perfecting our language detection logic.

// Our middleware configuration
import createMiddleware from 'next-intl/middleware';
import { routing } from '@/i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};

Challenge 2: Translation File Management

Managing translation files for 12 languages is like managing 12 different projects. We developed some automation scripts to help us:

// Our translation management script
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);
    }
  }
};

Challenge 3: The Pitfalls of Number Formatting

This was one of the most interesting challenges. Did you know that different countries have different number formats? Germany uses commas as decimal separators, the US uses periods. We ran into many issues when handling currency calculations until we implemented proper localized formatting.

SEO Optimization: Making Search Engines Love Us

The Power of Static Site Generation

We decided to use Static Site Generation (SSG), which was probably one of the best decisions we made. Not only are pages loading incredibly fast, but SEO results are excellent too.

// Generate static pages for each language
export const dynamic = 'force-static';

export function generateStaticParams() {
  return routing.locales
    .filter(locale => locale !== routing.defaultLocale)
    .map(locale => ({ locale }));
}

The Art of Metadata Generation

Generating correct metadata for each language requires some finesse. We developed a system to automatically generate SEO-friendly metadata:

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,
    },
  };
}

Performance Optimization: Making User Experience Smoother

The Importance of Image Optimization

We spent a lot of time optimizing image loading. Adopting AVIF and WebP formats improved our page loading speed by 40%.

// Image configuration in 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],
}

Caching Strategy: Our Secret Weapon

Implementing the right caching strategy took some trial and error. We ended up with a layered caching strategy:

async headers() {
  return [
    // Static assets use strong caching
    {
      source: '/(.*).(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)',
      headers: [{
        key: 'Cache-Control',
        value: 'public, max-age=31536000, immutable',
      }],
    },
    // Pages use negotiation caching
    {
      source: '/(.*)',
      headers: [{
        key: 'Cache-Control',
        value: 'public, max-age=0, must-revalidate',
      }],
    },
  ];
}

User Experience Design: Making Everyone Feel Welcome

The Challenge of Responsive Design

Ensuring our calculators work properly on all devices is an ongoing challenge. We adopted a mobile-first approach:

// Mobile-first responsive layout
<div className="flex flex-col md:flex-row gap-4">
  <div className="w-full md:w-1/2">
    {/* Calculator interface */}
  </div>
  <div className="w-full md:w-1/2">
    {/* Results display */}
  </div>
</div>

Accessibility: Not Just for Compliance

We realized that accessibility isn't just a legal requirement โ€” it's the right thing to do. We added appropriate ARIA labels for every interactive element:

<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>

Development Toolchain: Making Development More Efficient

Error Monitoring: Our Safety Net

We integrated Sentry for error monitoring, which helped us discover many issues we never anticipated:

export default withSentryConfig(withNextIntl(nextConfig), {
  org: 'my-projects-pd',
  project: 'free-calculators',
  widenClientFileUpload: true,
  disableLogger: true,
  automaticVercelMonitors: true,
});

Automated Translation Management

We developed some scripts to manage translation files, and these scripts are now an indispensable part of our workflow.

Lessons We Learned

1. Consider Internationalization from the Start

If you think you might need multi-language support, design it from the beginning. Adding internationalization later is much more difficult than designing it from the start.

2. User Experience Trumps Technical Showmanship

We spent a lot of time optimizing performance, but ultimately what users care about most is whether the interface is intuitive and calculations are accurate.

3. Automation is Key

Manually managing translations for 12 languages is unrealistic. Automation scripts are the lifeline of our project.

4. Test, Test, and Test Again

Different languages have different text lengths, different number formats, and different cultural expectations. Thorough testing is essential.

Future Plans

We're considering adding more languages, improving our translation management workflow, and exploring the possibility of AI-assisted translation. But most importantly, we want to continue providing users with the best calculator experience.

Conclusion

Building multilingual web applications is challenging, but it's also an incredibly rewarding experience. Through smart technology choices and extensive testing, we successfully created a platform that truly serves users worldwide.

If you're considering building multilingual applications, I hope this article gives you some inspiration. Remember, every challenge is a learning opportunity, and every mistake is a stepping stone to success.


Project Link: https://www.freecalculators.app Tech Stack: Next.js 15, React 19, TypeScript, Tailwind CSS, next-intl