Skip to main content
Engineering7 min readFebruary 28, 2026

Building Multilingual Web Applications

Internationalization is more than translating strings. Here's how to architect web applications that work naturally across languages, locales, and cultural contexts.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

Internationalization vs Localization: Understanding the Difference

These terms are used interchangeably but represent distinct engineering concerns. Internationalization (i18n) is the architecture work — building your application so it can support multiple languages and locales without code changes. Localization (l10n) is the content work — translating text, adapting formats, and adjusting cultural references for specific markets.

The critical insight: i18n is an architectural decision that must happen early. Retrofitting internationalization onto a mature application means touching every file that displays text, reformatting every date and number display, restructuring layouts that break with longer translations, and modifying database schemas that assumed a single language. This work typically costs 3-5x more than building i18n into the architecture from the start.

Even if you only need one language at launch, establishing the i18n architecture costs very little upfront and saves enormous effort later. Instead of hardcoding "Submit" in a button, you write t('form.submit') and define the string in a translation file. The engineering effort is nearly identical, but the second approach means adding German support later requires only translation files, not a codebase-wide search and replace.

The scope of i18n extends beyond text translation. Dates, numbers, currencies, sorting orders, pluralization rules, text direction (left-to-right vs right-to-left), and even color associations vary across locales. A well-internationalized application handles all of these through locale-aware APIs rather than hardcoded assumptions.


Architecture for Multilingual Support

The foundation of i18n architecture is separating translatable content from application logic. Every user-facing string lives in translation files organized by locale, and the application references strings by key rather than embedding text directly.

For Nuxt applications, the @nuxtjs/i18n module provides comprehensive i18n support:

locales/
 en.json # English translations
 es.json # Spanish translations
 de.json # German translations
{
 "nav": {
 "home": "Home",
 "about": "About Us",
 "contact": "Contact"
 },
 "hero": {
 "title": "Build Software That Scales",
 "subtitle": "Custom development for growing businesses"
 }
}

Components reference translations through the $t() function or the useI18n() composable:

<template>
 <h1>{{ $t('hero.title') }}</h1>
 <p>{{ $t('hero.subtitle') }}</p>
</template>

Organize translation keys by feature or page, not by UI element type. hero.title is better than headings.heroTitle because it keeps related translations together and makes it clear which part of the application uses each string.

URL strategy is a consequential decision. Three approaches exist:

Path prefix (/en/about, /es/about): Clean, SEO-friendly, and each locale has distinct URLs. This is the recommended approach for most applications because search engines treat each language version as a separate page and can index them independently.

Subdomain (en.example.com, es.example.com): Separates locales at the infrastructure level. Useful when different language versions have different content or different CDN configurations, but adds DNS and certificate management complexity.

Query parameter (/about?lang=es): Simple to implement but poor for SEO because search engines may not crawl parameter variations, and users cannot share locale-specific URLs cleanly.

For SEO across languages, implement hreflang tags that tell search engines which page serves which language. This prevents duplicate content penalties and ensures users find the correct language version in search results. Your technical SEO configuration must account for multilingual URL structures.


Handling Dynamic Content and Formatting

Static string translation is the straightforward part. The complexity emerges with dynamic content — pluralization, interpolation, dates, numbers, and user-generated content.

Pluralization varies dramatically across languages. English has two forms: singular and plural. Arabic has six forms. Russian has three. Your i18n library must support locale-specific plural rules:

{
 "items": {
 "count": "No items | {count} item | {count} items"
 }
}

The Intl API handles formatting without third-party libraries:

// Date formatting
new Intl.DateTimeFormat('de-DE', {
 year: 'numeric',
 month: 'long',
 day: 'numeric'
}).format(date);
// "7. Marz 2026"

// Number formatting
new Intl.NumberFormat('de-DE', {
 style: 'currency',
 currency: 'EUR'
}).format(1234.56);
// "1.234,56 EUR"

Note that German uses periods for thousands separators and commas for decimals — the opposite of English. Hardcoding number formatting guarantees incorrect display for some locales.

Text expansion is a layout concern that catches teams off guard. German text is typically 30% longer than English. Finnish can be 40% longer. A button that fits "Submit" perfectly will overflow with "Absenden" or "Laehetae." Design layouts with flexible widths, and test with the longest translation to ensure nothing breaks. CSS text-overflow: ellipsis is a safety net, not a solution — truncated translations are unusable.

Right-to-left (RTL) languages like Arabic and Hebrew require layout mirroring. Navigation that flows left-to-right must flip to right-to-left. Text alignment reverses. Padding and margin directions swap. CSS logical properties (margin-inline-start instead of margin-left, padding-block-end instead of padding-bottom) handle this automatically when the document direction changes:

.card {
 margin-inline-start: 1rem;
 padding-inline-end: 2rem;
 text-align: start;
}

These properties respond to the document's dir attribute, applying the correct physical direction for both LTR and RTL languages without any CSS overrides.


Content Management and Translation Workflow

The technical architecture is one half of i18n. The other half is the workflow for creating and maintaining translations across the application lifecycle.

For small applications with a few hundred strings, JSON translation files in the repository work fine. Developers add English strings, and translators update the locale files. The translation files are version-controlled with the code, and deployments include all translations.

For larger applications, translation management platforms like Crowdin, Lokalise, or Phrase provide better workflows. Developers push source strings to the platform, translators work in a dedicated interface with context and glossaries, and translated strings are pulled back into the codebase automatically. These platforms handle translation memory (reusing previously translated phrases), progress tracking, and quality assurance.

For content stored in a headless CMS, translations live in the CMS rather than in code. Most headless CMS platforms support locale-specific field variants — each content entry has separate fields for each supported language. This is the correct approach for content-heavy sites where non-developers manage translations.

Machine translation (Google Translate, DeepL) is useful for generating first drafts but insufficient for production quality. Automated translations often miss context, produce awkward phrasing, and fail on domain-specific terminology. Use machine translation to create initial drafts, then have human translators review and refine. This hybrid approach cuts translation costs by 40-60% compared to translating from scratch while maintaining professional quality.

Plan for translation incompleteness. Not every string will be translated into every locale on day one. Your application needs a fallback strategy — typically, display the default language (English) for untranslated strings rather than showing translation keys. Log missing translations in production so you can track and address gaps over time without breaking the user experience.

Internationalization is one of those engineering investments that seems optional until the business needs it, at which point the cost of not having done it earlier becomes painfully clear. Build the architecture from the start, even if you launch in one language.