Type-safe translation store for managing i18n locales with full TypeScript support
npm install i18n-typed-store-react> ⚠️ WARNING: The library API is under active development and may change significantly between versions. Use exact versions in package.json and read the changelog carefully when updating.
React integration for i18n-typed-store - a type-safe translation store for managing i18n locales with full TypeScript support. Provides React hooks, components, and SSR utilities for seamless integration with React applications.
- ✅ React Hooks - useI18nTranslation, useI18nTranslationLazy, useI18nLocale
- ✅ React Suspense Support - Built-in support for React Suspense with lazy loading
- ✅ Provider Component - I18nTypedStoreProvider for providing translation context
- ✅ SSR/SSG Support - Utilities for Next.js and other SSR frameworks
- ✅ Type-Safe - Full TypeScript support with autocomplete and go-to definition
- ✅ Safe Component - Error-safe component for accessing translations
- ✅ Locale Management - Hook for accessing and changing locales with automatic updates
``bash`
npm install i18n-typed-store-react
`bash`
yarn add i18n-typed-store-react
`bash`
pnpm add i18n-typed-store-react
First, create your translation store using i18n-typed-store:
`typescript
// store.ts
import { createTranslationStore } from 'i18n-typed-store';
import type CommonTranslationsEn from './translations/common/en';
import { TRANSLATIONS, LOCALES } from './constants';
export interface ITranslationStoreTypes extends Record
common: CommonTranslationsEn;
}
export const store = createTranslationStore({
namespaces: TRANSLATIONS,
locales: LOCALES,
loadModule: async (locale, namespace) => {
return await import(./translations/${namespace}/${locale}.tsx);`
},
extractTranslation: (module) => new module.default(),
defaultLocale: 'en',
}).type
`typescript
// constants.ts
export const TRANSLATIONS = {
common: 'common',
} as const;
export const LOCALES = {
en: 'en',
ru: 'ru',
} as const;
`
`tsx
// App.tsx
import { I18nTypedStoreProvider } from 'i18n-typed-store-react';
import { store } from './store';
import { MyComponent } from './MyComponent';
function App() {
return (
);
}
`
`tsx
// MyComponent.tsx
import { useI18nTranslation, useI18nLocale } from 'i18n-typed-store-react';
import { TRANSLATIONS, LOCALES } from './constants';
import type { ITranslationStoreTypes } from './store';
function MyComponent() {
const translations = useI18nTranslation
const { locale, setLocale } = useI18nLocale
if (!translations) {
return
return (
{translations.greeting}
$3
For better type safety and cleaner code, create typed wrapper hooks:
`typescript
// hooks/useTranslation.ts
import { useI18nTranslation } from 'i18n-typed-store-react/useI18nTranslation';
import type { TRANSLATIONS, LOCALES } from '../constants';
import type { ITranslationStoreTypes } from '../store';export const useTranslation = (translation: K) => {
return useI18nTranslation(translation);
};
``typescript
// hooks/useTranslationLazy.ts
import { useI18nTranslationLazy } from 'i18n-typed-store-react/useI18nTranslationLazy';
import type { TRANSLATIONS, LOCALES } from '../constants';
import type { ITranslationStoreTypes } from '../store';export const useTranslationLazy = (translation: K) => {
return useI18nTranslationLazy(translation);
};
`Now you can use them with full type inference:
`tsx
// MyComponent.tsx
import { useTranslation } from './hooks/useTranslation';
import { useI18nLocale } from 'i18n-typed-store-react';function MyComponent() {
const translations = useTranslation('common');
const { locale, setLocale } = useI18nLocale();
if (!translations) {
return
Loading...;
} return (
{translations.title}
{translations.greeting}
);
}
`React Suspense Support
Use
useI18nTranslationLazy with React Suspense for automatic loading states:`tsx
// MyComponent.tsx
import { Suspense } from 'react';
import { useTranslationLazy } from './hooks/useTranslationLazy';function MyComponent() {
// This hook throws a promise if translation is not loaded (for Suspense)
const translations = useTranslationLazy('common');
return (
{translations.title}
{translations.greeting}
);
}
``tsx
// App.tsx
import { Suspense } from 'react';
import { I18nTypedStoreProvider } from 'i18n-typed-store-react';
import { store } from './store';
import { MyComponent } from './MyComponent';function App() {
return (
Loading translations...
API Reference
$3
Provider component that wraps your application to provide translation store context.
`tsx
{children}
`Props:
-
store - Translation store instance (created with createTranslationStore)
- suspenseMode - Suspense mode: 'once' | 'first-load-locale' | 'change-locale' (default: 'first-load-locale')
- 'once' - Suspense only on first load
- 'first-load-locale' - Suspense on first load for each locale
- 'change-locale' - Suspense on every locale change
- children - React children$3
Hook for accessing translations with automatic loading. Returns
undefined if translation is not yet loaded.`tsx
// Direct usage
const translations = useI18nTranslation<
typeof TRANSLATIONS,
typeof LOCALES,
ITranslationStoreTypes,
'common'
>('common', fromCache?: boolean);// Typed wrapper (recommended)
import { useI18nTranslation } from 'i18n-typed-store-react/useI18nTranslation';
import type { TRANSLATIONS, LOCALES } from './constants';
import type { ITranslationStoreTypes } from './store';
export const useTranslation = (translation: K) => {
return useI18nTranslation(translation);
};
// Usage
const translations = useTranslation('common');
if (translations) {
console.log(translations.greeting);
}
`Parameters:
-
namespace - Namespace key to load translations for
- fromCache - Whether to use cached translation if available (default: true)Returns: Translation object for the specified namespace, or
undefined if not loaded$3
Hook for accessing translations with React Suspense support. Throws a promise if translation is not loaded.
`tsx
// Direct usage
const translations = useI18nTranslationLazy<
typeof TRANSLATIONS,
typeof LOCALES,
ITranslationStoreTypes,
'common'
>('common', fromCache?: boolean);// Typed wrapper (recommended)
import { useI18nTranslationLazy } from 'i18n-typed-store-react/useI18nTranslationLazy';
import type { TRANSLATIONS, LOCALES } from './constants';
import type { ITranslationStoreTypes } from './store';
export const useTranslationLazy = (translation: K) => {
return useI18nTranslationLazy(translation);
};
// Usage
function MyComponent() {
const translations = useTranslationLazy('common');
return
{translations.greeting};
}
`Parameters:
-
namespace - Namespace key to load translations for
- fromCache - Whether to use cached translation if available (default: true)Returns: Translation object for the specified namespace (never
undefined)Throws: Promise if translation is not yet loaded (for React Suspense)
$3
Hook for accessing and managing the current locale. Supports SSR/SSG by using
useSyncExternalStore.`tsx
const { locale, setLocale } = useI18nLocale();
`Returns:
-
locale - Current locale key
- setLocale - Function to change the current localeExample:
`tsx
function LocaleSwitcher() {
const { locale, setLocale } = useI18nLocale(); return (
);
}
`$3
Component that safely extracts strings from translation objects, catching errors.
`tsx
N/A} errorHandler={(error) => console.error(error)}>
{() => translations.common.pages.main.title}
`Props:
-
children - Function that returns a string (called during render)
- errorComponent - Component to display if an error occurs (default: empty string)
- errorHandler - Optional error handler callbackSSR/SSG Support
$3
`typescript
// pages/_app.tsx
import { I18nTypedStoreProvider } from 'i18n-typed-store-react';
import { storeFactory } from '../lib/i18n';
import type { AppProps } from 'next/app';const store = storeFactory.type();
function MyApp({ Component, pageProps }: AppProps) {
return (
);
}
export default MyApp;
``typescript
// pages/index.tsx
import type { GetServerSidePropsContext } from 'next';
import { getLocaleFromRequest, initializeStore } from 'i18n-typed-store-react';
import { storeFactory } from '../lib/i18n';
import type { TranslationData } from '../lib/i18n';export async function getServerSideProps(context: GetServerSidePropsContext) {
const locale = getLocaleFromRequest(context, {
defaultLocale: 'en',
availableLocales: ['en', 'ru'],
cookieName: 'locale',
queryParamName: 'locale',
});
const store = storeFactory.type();
initializeStore(store, locale);
// Preload translations if needed
await store.translations.common.load(locale);
return {
props: {
locale,
},
};
}
`$3
`typescript
// app/layout.tsx
import { I18nTypedStoreProvider } from 'i18n-typed-store-react';
import { storeFactory } from '../lib/i18n';
import type { TranslationData } from '../lib/i18n';const store = storeFactory.type();
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
``typescript
// app/page.tsx
import { getLocaleFromRequest, initializeStore } from 'i18n-typed-store-react';
import { storeFactory } from '../lib/i18n';
import type { TranslationData } from '../lib/i18n';
import { headers, cookies } from 'next/headers';export default async function Page() {
const headersList = await headers();
const cookieStore = await cookies();
const locale = getLocaleFromRequest(
{
headers: Object.fromEntries(headersList),
cookies: Object.fromEntries(cookieStore),
},
{
defaultLocale: 'en',
availableLocales: ['en', 'ru'],
}
);
const store = storeFactory.type();
initializeStore(store, locale);
await store.translations.common.load(locale);
return
...;
}
`$3
####
getLocaleFromRequestGets locale from SSR request context (query params, cookies, headers).
`typescript
function getLocaleFromRequest>(context: RequestContext, options: GetLocaleFromRequestOptions): keyof L;
`Parameters:
-
context - Request context with query, cookies, and headers
- options - Options object:
- defaultLocale - Default locale to use if locale cannot be determined
- availableLocales - Array of available locale keys for validation
- headerName - Header name to read locale from (default: 'accept-language')
- cookieName - Cookie name to read locale from
- queryParamName - Query parameter name to read locale from (default: 'locale')
- parseAcceptLanguage - Whether to parse Accept-Language header (default: true)Example:
`typescript
const locale = getLocaleFromRequest(context, {
defaultLocale: 'en',
availableLocales: ['en', 'ru'],
cookieName: 'locale',
queryParamName: 'locale',
headerName: 'accept-language',
parseAcceptLanguage: true,
});
`####
initializeStoreInitializes translation store with a specific locale for SSR.
`typescript
function initializeStore(store: TranslationStore, locale: keyof L): void;
`Parameters:
-
store - Translation store instance
- locale - Locale to initialize withExample:
`typescript
const locale = getLocaleFromRequest(context, {
defaultLocale: 'en',
availableLocales: ['en', 'ru'],
});const store = storeFactory.type();
initializeStore(store, locale);
`Complete Example
`typescript
// constants.ts
export const TRANSLATIONS = {
common: 'common',
errors: 'errors',
} as const;export const LOCALES = {
en: 'en',
ru: 'ru',
} as const;
``typescript
// translations/common/en.tsx
import { createPluralSelector } from 'i18n-typed-store';const plur = createPluralSelector('en');
export default class CommonTranslationsEn {
title = 'Welcome';
greeting = 'Hello, World!';
buttons = {
save: 'Save',
cancel: 'Cancel',
};
items = (count: number) =>
count +
' ' +
plur(count, {
one: 'item',
other: 'items',
});
}
``typescript
// store.ts
import { createTranslationStore } from 'i18n-typed-store';
import type CommonTranslationsEn from './translations/common/en';
import { TRANSLATIONS, LOCALES } from './constants';export interface ITranslationStoreTypes extends Record {
common: CommonTranslationsEn;
}
export const store = createTranslationStore({
namespaces: TRANSLATIONS,
locales: LOCALES,
loadModule: async (locale, namespace) => {
return await import(
./translations/${namespace}/${locale}.tsx);
},
extractTranslation: (module) => new module.default(),
defaultLocale: 'en',
}).type();
``typescript
// hooks/useTranslation.ts
import { useI18nTranslation } from 'i18n-typed-store-react/useI18nTranslation';
import type { TRANSLATIONS, LOCALES } from '../constants';
import type { ITranslationStoreTypes } from '../store';export const useTranslation = (translation: K) => {
return useI18nTranslation(translation);
};
``tsx
// App.tsx
import { I18nTypedStoreProvider } from 'i18n-typed-store-react';
import { store } from './store';
import { MyComponent } from './MyComponent';function App() {
return (
);
}
export default App;
``tsx
// MyComponent.tsx
import { useTranslation } from './hooks/useTranslation';
import { useI18nLocale } from 'i18n-typed-store-react';function MyComponent() {
const translations = useTranslation('common');
const { locale, setLocale } = useI18nLocale();
if (!translations) {
return
Loading...;
} return (
{translations.title}
{translations.greeting}
{translations.items(5)}
);
}
`Type Safety
All hooks and components are fully type-safe:
`tsx
// ✅ TypeScript knows all available translation keys
const translations = useTranslation('common');
if (translations) {
const title = translations.title; // ✅ Type-safe
const greeting = translations.greeting; // ✅ Type-safe
}// ❌ TypeScript error: 'invalidKey' doesn't exist
// const invalid = translations.invalidKey;
// ✅ TypeScript knows all available locales
const { locale, setLocale } = useI18nLocale();
setLocale('en'); // ✅ Type-safe
setLocale('ru'); // ✅ Type-safe
// ❌ TypeScript error: 'fr' is not a valid locale
// setLocale('fr');
``Contributions are welcome! Please feel free to submit a Pull Request.
MIT
Alexander Lvov
- i18n-typed-store - Core library
- React Example - Complete working example with React, TypeScript, and all features demonstrated