Superfunction

@spfn/i18n

@spfn/i18n is a small, content-agnostic internationalization runtime for SPFN and React applications. Your application owns its translation catalogs and locale resolution; the package provides interpolation, fallback lookup, server helpers, and a React context.

Installation

pnpm add @spfn/i18n

Install React only when using the client entry point:

pnpm add react

Catalogs

Catalogs are plain objects organized as locale -> namespace -> key -> message:

import type { LocaleCatalogs } from '@spfn/i18n';

export const catalogs: LocaleCatalogs = {
    en: {
        common: {
            greeting: 'Hello, {name}',
            save: 'Save',
        },
    },
    ko: {
        common: {
            greeting: '안녕하세요, {name}',
        },
    },
};

Messages support named string and number placeholders. A missing variable remains visible as its original placeholder, and an unknown message key is returned unchanged.

Server usage

Configure the server registry once during application startup:

import { configureI18n, getT } from '@spfn/i18n/server';
import { catalogs } from './catalogs';

configureI18n({ catalogs, fallbackLocale: 'en' });

const t = getT('common', user.locale);
console.log(t('greeting', { name: user.name }));

Locale selection stays with the application. It can come from an authenticated SPFN profile, a route parameter, a request header, or any other application policy.

React and Next.js usage

Resolve only the namespaces needed by the client subtree and pass them across the server/client boundary:

// Server component
import { getClientMessages } from '@spfn/i18n/server';
import { I18nProvider } from '@spfn/i18n/client';

export function LocalizedLayout({
    locale,
    children,
}: {
    locale: string;
    children: React.ReactNode;
})
{
    const messages = getClientMessages(locale, ['common']);

    return (
        <I18nProvider locale={locale} messages={messages}>
            {children}
        </I18nProvider>
    );
}

Client components use namespace-scoped translators:

'use client';

import { useLocale, useT } from '@spfn/i18n/client';

export function Greeting({ name }: { name: string })
{
    const locale = useLocale();
    const t = useT('common');

    return <p lang={locale}>{t('greeting', { name })}</p>;
}

getClientMessages merges fallback messages first and locale-specific messages second, so the client receives a complete, serializable dictionary without a loading flash.

Core API

The package root can also be used independently of React or SPFN:

import { createTranslator } from '@spfn/i18n';

const t = createTranslator(
    { greeting: 'Bonjour, {name}' },
    { farewell: 'Goodbye' },
);

t('greeting', { name: 'Ada' }); // "Bonjour, Ada"
t('farewell'); // "Goodbye"
t('missing'); // "missing"

Scope

This runtime intentionally does not load catalog files, detect locales, format dates or numbers, or implement plural rules. Use platform Intl APIs for locale-aware formatting and keep catalog loading and locale policy in the consuming application.

Interpolation returns strings; it does not sanitize HTML. React escapes text values by default, but applications must sanitize messages before inserting them as raw HTML.

License

MIT