npm package: @shopkit/i18n
Description: Internationalization utilities for Next.js storefronts
Latest version: 0.1.0

# @shopkit/i18n

Internationalization utilities for Next.js storefronts with next-intl integration.

## Features

- **next-intl Integration**: Built on top of next-intl for robust i18n
- **Server/Client Separation**: Next.js App Router compatible architecture
- **Locale Detection**: Cookie, header, and default-based locale detection
- **Formatting Utilities**: Currency, date, number, and relative time formatting
- **Translation Provider**: React context for page builder translation access
- **Middleware Support**: Easy locale routing middleware setup

## Installation

```bash
npm install @shopkit/i18n
# or
bun add @shopkit/i18n
```

### Peer Dependencies

```bash
npm install next next-intl react
```

## Quick Start

### 1. Configure i18n

```typescript
// src/i18n/config.ts
import { createI18nConfig } from '@shopkit/i18n';

export const i18nConfig = createI18nConfig({
  locales: ['en', 'es', 'fr', 'de'],
  defaultLocale: 'en',
  localeDetection: ['cookie', 'header', 'default'],
});

export const { locales, defaultLocale, isValidLocale } = i18nConfig;
```

### 2. Setup Middleware

```typescript
// middleware.ts
import { createLocaleMiddleware } from '@shopkit/i18n/middleware';
import { i18nConfig } from './src/i18n/config';

export default createLocaleMiddleware(i18nConfig);

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

### 3. Server-Side Locale Detection

```typescript
// src/i18n/server.ts
import { createGetLocale, createI18nRequestConfig } from '@shopkit/i18n/server';
import { i18nConfig } from './config';

export const getLocale = createGetLocale(i18nConfig);
export const i18nRequestConfig = createI18nRequestConfig({
  locales: i18nConfig.locales,
  defaultLocale: i18nConfig.defaultLocale,
  messagesPath: './messages',
});
```

### 4. Client-Side Formatting

```tsx
// components/ProductPrice.tsx
'use client';

import { formatCurrency } from '@shopkit/i18n/client';
import { useLocale } from 'next-intl';

export function ProductPrice({ price }: { price: number }) {
  const locale = useLocale();

  return <span>{formatCurrency(price, locale, 'USD')}</span>;
}
```

## Subpath Exports

```typescript
// Main entry (types and config factories only)
import { createI18nConfig, createLocaleValidator } from '@shopkit/i18n';
import type { I18nConfig, Locale } from '@shopkit/i18n';

// Server utilities
import { createGetLocale, createI18nRequestConfig, createSetLocaleCookie } from '@shopkit/i18n/server';

// Client utilities (formatters, hooks)
import { formatCurrency, formatDate, formatNumber, formatPercent, formatRelativeTime } from '@shopkit/i18n/client';
import { TranslationProvider, useTranslation } from '@shopkit/i18n/client';

// Middleware
import { createLocaleMiddleware } from '@shopkit/i18n/middleware';
```

## Configuration

### createI18nConfig

```typescript
import { createI18nConfig } from '@shopkit/i18n';

const config = createI18nConfig({
  // Required
  locales: ['en', 'es', 'fr'],      // Supported locales
  defaultLocale: 'en',               // Fallback locale

  // Optional
  localeDetection: ['cookie', 'header', 'default'],  // Detection order
  cookieName: 'NEXT_LOCALE',         // Cookie name for locale storage
});

// Returns:
config.locales;          // ['en', 'es', 'fr']
config.defaultLocale;    // 'en'
config.isValidLocale('es');  // true
config.isValidLocale('xx');  // false
```

## Server Utilities

### createGetLocale

```typescript
import { createGetLocale } from '@shopkit/i18n/server';

const getLocale = createGetLocale(i18nConfig);

// In a server component or API route
const locale = await getLocale();  // Returns detected locale
```

### createSetLocaleCookie

```typescript
import { createSetLocaleCookie } from '@shopkit/i18n/server';

const setLocaleCookie = createSetLocaleCookie(i18nConfig);

// Set locale preference
await setLocaleCookie('es');
```

### createI18nRequestConfig

```typescript
import { createI18nRequestConfig } from '@shopkit/i18n/server';

const i18nRequestConfig = createI18nRequestConfig({
  locales: ['en', 'es'],
  defaultLocale: 'en',
  messagesPath: './messages',  // Path to translation files
});

// Use with next-intl
export default i18nRequestConfig;
```

## Client Utilities

### Formatting Functions

```typescript
import {
  formatCurrency,
  formatDate,
  formatNumber,
  formatPercent,
  formatRelativeTime,
} from '@shopkit/i18n/client';

// Currency formatting
formatCurrency(99.99, 'en-US', 'USD');  // '$99.99'
formatCurrency(99.99, 'de-DE', 'EUR');  // '99,99 €'

// Date formatting
formatDate(new Date(), 'en-US');  // 'Feb 10, 2026'
formatDate(new Date(), 'en-US', { dateStyle: 'full' });  // 'Tuesday, February 10, 2026'

// Number formatting
formatNumber(1234567.89, 'en-US');  // '1,234,567.89'
formatNumber(1234567.89, 'de-DE');  // '1.234.567,89'

// Percentage formatting
formatPercent(0.1234, 'en-US');  // '12%'
formatPercent(0.1234, 'en-US', { maximumFractionDigits: 2 });  // '12.34%'

// Relative time
formatRelativeTime(-1, 'day', 'en-US');   // 'yesterday'
formatRelativeTime(2, 'hour', 'en-US');   // 'in 2 hours'
```

### TranslationProvider

For page builder integration:

```tsx
import { TranslationProvider } from '@shopkit/i18n/client';

function PageBuilderWrapper({ children, translations }) {
  return (
    <TranslationProvider translations={translations} locale="en">
      {children}
    </TranslationProvider>
  );
}
```

### useTranslation Hook

```tsx
'use client';

import { useTranslation } from '@shopkit/i18n/client';

function MyComponent() {
  const { t, locale } = useTranslation();

  return (
    <div>
      <h1>{t('welcome.title')}</h1>
      <p>{t('welcome.description', { name: 'John' })}</p>
      <span>Current locale: {locale}</span>
    </div>
  );
}
```

## Middleware

### createLocaleMiddleware

```typescript
// middleware.ts
import { createLocaleMiddleware } from '@shopkit/i18n/middleware';
import { i18nConfig } from './src/i18n/config';

export default createLocaleMiddleware(i18nConfig);

// Optionally configure matcher
export const config = {
  matcher: [
    // Match all paths except static files and API routes
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};
```

## Translation Files

Create JSON translation files:

```
messages/
  en.json
  es.json
  fr.json
```

```json
// messages/en.json
{
  "common": {
    "addToCart": "Add to Cart",
    "checkout": "Checkout",
    "continueShopping": "Continue Shopping"
  },
  "product": {
    "outOfStock": "Out of Stock",
    "inStock": "In Stock",
    "price": "Price: {price}"
  }
}
```

## Type Reference

### I18nConfig

```typescript
interface I18nConfig {
  locales: readonly string[];
  defaultLocale: string;
  isValidLocale: (locale: string) => boolean;
}
```

### I18nConfigOptions

```typescript
interface I18nConfigOptions {
  locales: string[];
  defaultLocale: string;
  localeDetection?: LocaleDetectionMethod[];
  cookieName?: string;
}

type LocaleDetectionMethod = 'cookie' | 'header' | 'default';
```

### TranslationParams

```typescript
interface TranslationParams {
  [key: string]: string | number | boolean;
}
```

## Integration with @shopkit/builder

```typescript
import { createPageBuilder } from '@shopkit/builder';
import { TranslationProvider } from '@shopkit/i18n/client';

// Wrap page builder output with translations
function LocalizedPage({ pageContent, translations, locale }) {
  return (
    <TranslationProvider translations={translations} locale={locale}>
      {pageContent}
    </TranslationProvider>
  );
}
```

## License

MIT