Learn
Internationalization

i18n MVP Implementation Guide

Overview

This document demonstrates how to use the new internationalization formatting utilities without full translation infrastructure.

Quick Start

1. In Components (Client-Side)

Use the React hooks for automatic locale detection:

import { useFormat } from '~/lib/i18n';
 
export default function MyComponent() {
  const { formatDate, formatCurrency, formatNumber } = useFormat();
  
  const createdAt = new Date('2025-12-03');
  const price = 99.99;
  const users = 1234567;
  
  return (
    <div>
      <p>Created: {formatDate(createdAt)}</p>
      <p>Price: {formatCurrency(price)}</p>
      <p>Users: {formatNumber(users)}</p>
    </div>
  );
}

2. In Loaders/Actions (Server-Side)

Import utilities directly:

import { formatDate, formatCurrency } from '~/lib/i18n';
 
export async function loader({ request }: LoaderFunctionArgs) {
  const locale = request.headers.get('Accept-Language')?.split(',')[0] || 'en-US';
  
  const data = {
    createdAt: formatDate(new Date(), locale),
    price: formatCurrency(99.99, 'USD', locale),
  };
  
  return json(data);
}

Available Functions

Dates

formatDate(new Date());
// Output: "Dec 3, 2025" (en-US)
// Output: "3 déc. 2025" (fr-FR)
// Output: "2025年12月3日" (ja-JP)
 
formatDateTime(new Date());
// Output: "Dec 3, 2025, 1:05 AM" (en-US)
 
formatRelativeTime(new Date(Date.now() - 3600000));
// Output: "1 hour ago" (en-US)
// Output: "il y a 1 heure" (fr-FR)

Currency

formatCurrency(1234.56, 'USD', 'en-US');
// Output: "$1,234.56"
 
formatCurrency(1234.56, 'EUR', 'de-DE');
// Output: "1.234,56 €"
 
formatCurrency(1234.56, 'JPY', 'ja-JP');
// Output: "¥1,235"

Numbers

formatNumber(1234567, 'en-US');
// Output: "1,234,567"
 
formatNumber(1234567, 'de-DE');
// Output: "1.234.567"
 
formatPercent(0.75, 'en-US', 1);
// Output: "75.0%"
 
formatFileSize(1536000, 'en-US');
// Output: "1.46 MB"

Migration Examples

Before (Hardcoded)

// ❌ Not locale-aware
<p>Created: {new Date(client.createdAt).toLocaleString()}</p>
<p>Price: ${product.price} USD</p>
<p>Users: {organization.memberCount}</p>

After (i18n-Ready)

// ✅ Respects user locale
import { useFormat } from '~/lib/i18n';
 
function Component() {
  const { formatDateTime, formatCurrency, formatNumber } = useFormat();
  
  return (
    <>
      <p>Created: {formatDateTime(client.createdAt)}</p>
      <p>Price: {formatCurrency(product.price)}</p>
      <p>Users: {formatNumber(organization.memberCount)}</p>
    </>
  );
}

Custom Formatting Options

All functions accept Intl API options:

// Custom date format
formatDate(new Date(), 'en-US', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});
// Output: "Tuesday, December 3, 2025"
 
// Custom number format
formatNumber(1234.5678, 'en-US', {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});
// Output: "1,234.57"

Locale Detection Priority

The system detects locale in this order:

  1. User DB preference (if user is logged in and has set locale)
  2. Cookie preference (locale cookie)
  3. Browser Accept-Language header
  4. Default fallback (en-US)

Future Expansion Path

When you're ready for full translation:

  1. Install i18next or react-intl
  2. Extract hardcoded strings to translation files
  3. Keep these formatting utilities (they work alongside translation libraries)
  4. Add language switcher UI

The current implementation provides a solid foundation without the overhead of managing thousands of translation strings.

Performance Notes

  • All formatting uses native Intl API (built into browsers)
  • Zero external dependencies
  • Zero bundle size increase beyond our utility code (~2KB)
  • Formatting is fast (microseconds per call)

Browser Support

Intl API is supported in all modern browsers:

  • Chrome 24+
  • Firefox 29+
  • Safari 10+
  • Edge (all versions)

For older browsers, formatting gracefully falls back to default.