npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shopkit/i18n

v0.1.0

Published

Internationalization utilities for Next.js storefronts

Downloads

118

Readme

@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

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

Peer Dependencies

npm install next next-intl react

Quick Start

1. Configure i18n

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

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

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

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

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

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

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

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

const setLocaleCookie = createSetLocaleCookie(i18nConfig);

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

createI18nRequestConfig

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

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:

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

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

useTranslation Hook

'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

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

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

I18nConfigOptions

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

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

TranslationParams

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

Integration with @shopkit/builder

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