npm package

i18n‑typesafe‑interpolation

Fully type-safe i18n with autocomplete for translation keys and interpolation variables. Minimal footprint, no codegen - TypeScript infers required {{placeholder}} variables directly from your translation strings. Zero runtime overhead.

Installation

npm install i18n-typesafe-interpolation
# or
pnpm add i18n-typesafe-interpolation
# or
bun add i18n-typesafe-interpolation

Setup

Define your translations as const so TypeScript can infer string literal types.

// translations.ts
const EN = {
  common: {
    greeting: "Hello",
    welcome: "Welcome, {{name}}!",
    inbox: "You have {{count}} messages from {{sender}}",
  },
} as const;

export const resources = { EN } as const;
export type Translations = typeof resources.EN;

Wire up i18next and create the typed helpers once at the module level.

// i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { createTypedT } from 'i18n-typesafe-interpolation';
import { createNamespaceHook } from 'i18n-typesafe-interpolation/hooks';
import { resources, Translations } from './translations';

i18n.use(initReactI18next).init({
  resources,
  lng: 'EN',
  fallbackLng: 'EN',
  interpolation: { escapeValue: false },
});

export const t = createTypedT<Translations>(i18n);
export const useNS = createNamespaceHook<Translations>();

export default i18n;

createTypedT

Every t() call is validated at compile time. Pass a missing variable, extra options, or a wrong key - TypeScript catches it immediately, with no build step.

import { t } from './i18n';

t("common:greeting");                              // ✅ no options needed
t("common:welcome", { name: "Alice" });            // ✅ typed options
t("common:inbox", { count: "3", sender: "Bob" });  // ✅ all placeholders required

t("common:welcome");                               // ❌ TS error: missing { name }
t("common:greeting", { name: "Alice" });           // ❌ TS error: no placeholders expected
t("common:welcome", { typo: "Alice" });            // ❌ TS error: wrong key name

useNamespaceTranslation

Single namespace

Scope the hook to one namespace - keys stay short and cross-namespace access is a compile error.

const { t } = useNS('common');

t('greeting');                    // ✅ no options needed
t('welcome', { name: 'Alice' });  // ✅ only 'name' accepted
t('welcome');                     // ❌ TS error: missing { name }
t('welcome', { typo: 'Alice' });  // ❌ TS error: wrong key name

Array of namespaces

Compose multiple namespaces in one hook call - a single useTranslation under the hood avoids unnecessary re-renders. Keys require the namespace:key prefix and are still fully validated.

const { t } = useNS(['common', 'errors'] as const);

t('common:welcome', { name: 'Alice' }); // ✅
t('errors:notFound');                   // ✅
t('common:welcome');                    // ❌ TS error: missing { name }
t('errors:nonExistent');                // ❌ TS error: key doesn't exist
t('form:submit');                       // ❌ TS error: namespace not included