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