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

react-typesafe-translations

v1.3.0

Published

Co-located, type-checked translations for React components.

Readme

react-typesafe-translations

Sensible translations for the AI-assisted development era.

A fully type-safe internationalization library for React applications with zero build steps and minimal boilerplate. Designed for developer productivity and seamless integration with LLM coding assistants.


Features

  • Full Type Safety — Every translation key, parameter, and function signature is type-checked at compile time
  • Zero Build Steps — No code generation or plugins needed
  • IDE Integration — Autocomplete, go-to-definition, real-time type errors
  • Co-located Translations — Keep translations close to components
  • AI-Assistant Friendly — Compiler errors guide LLMs to add missing translations systematically
  • Automatic Code Splitting — Lazy-loaded components automatically lazy-load their translations
  • Lightweight — Minimal runtime using React's useSyncExternalStore
  • Function-based Interpolation — Type-safe translation functions
  • Configurable Locale Rules — Mark some languages as required, others as optional
  • No ICU Black Magic — Formatting and logic are handled in code, not strings
  • No External Dependencies — Pure TypeScript and React, no extra libraries
  • Small Bundle Size — Minimal impact on your app's size
  • No missing translations — Ensures all required languages have translations defined
  • Missing Key Detection — CLI and VS Code extension detect unused translation keys

Installation

npm install react-typesafe-translations
# or
yarn add react-typesafe-translations
# or
pnpm add react-typesafe-translations
# or
bun add react-typesafe-translations

Quick Start

1. Create the i18n utility

// utils/i18n.ts
import { createTranslationsFactory } from 'react-typesafe-translations';

export const i18n = createTranslationsFactory<'fi' | 'en', 'fi'>('fi');

export const setLanguage = i18n.setLanguage;
export const useLanguage = i18n.useLanguage;
export const useTranslations = i18n.useTranslations;
export type Translations = typeof i18n.Translations;

2. Define translations

// translations.ts
import type { Translations } from '~/utils/i18n';

export const translations = {
  welcome: {
    fi: 'Tervetuloa!',
    en: 'Welcome!',
  },
  greetUser: (name: string) => ({
    fi: `Hei, ${name}!`,
    en: `Hello, ${name}!`,
  }),
  itemCount: (count: number) => ({
    fi: `${count} ${count === 1 ? 'kohde' : 'kohdetta'}`,
    en: `${count} ${count === 1 ? 'item' : 'items'}`,
  }),
} satisfies Translations;

💡 Always use satisfies Translations — this enables full type checking and excess key detection.

3. Use in components

import { useTranslations } from '~/utils/i18n';
import { translations } from './translations';

export function WelcomeComponent() {
  const { t } = useTranslations(translations);

  return (
    <div>
      <h1>{t.welcome}</h1>
      <p>{t.greetUser('Alice')}</p>
      <span>{t.itemCount(5)}</span>
    </div>
  );
}

4. Change language

import { setLanguage } from '~/utils/i18n';

setLanguage('en');

AI-Assisted Translation Workflow

One of the most powerful aspects of react-typesafe-translations is how well it works with AI coding assistants. The co-location pattern and TypeScript's type system create a perfect feedback loop for LLM-driven translation management.

Adding a New Language

When you add a new language to RequiredLanguages, TypeScript immediately flags every missing translation as a compile error:

// utils/i18n.ts
// Before: only Finnish required
export const i18n = createTranslationsFactory<'fi' | 'en', 'fi'>('fi');

// After: adding English as required
export const i18n = createTranslationsFactory<'fi' | 'en', 'fi' | 'en'>('fi');

The moment you save this change, TypeScript will emit errors for every translation object missing English translations throughout your entire codebase.

LLM Agent Instructions

You can then instruct your AI assistant:

"Add English translations for all missing keys. Run the TypeScript compiler after each file to see remaining errors. Continue until all type errors are resolved."

The LLM agent will:

  1. Read the TypeScript errors to find files with missing translations
  2. Open each file and see the translation context co-located with the component
  3. Add appropriate English translations based on the Finnish text and component usage
  4. Verify the fix by checking that errors are resolved
  5. Move to the next file

This workflow is far superior to traditional i18n approaches where:

  • Translation files are centralized and separated from usage context
  • LLMs must hunt through the codebase to understand what each key means
  • There's no compiler feedback to verify completeness
  • Missing translations fail silently at runtime

Example: Before and After

Before (TypeScript error):

const translations = {
  welcome: {
    fi: 'Tervetuloa!',
    // ❌ Error: Property 'en' is missing
  },
} satisfies Translations;

After (LLM adds translation):

const translations = {
  welcome: {
    fi: 'Tervetuloa!',
    en: 'Welcome!', // ✅ LLM added this based on context
  },
} satisfies Translations;

The co-location means the LLM can see:

  • The original text to translate
  • The component code using the translation
  • The semantic meaning from variable names and JSX context
  • Any formatting functions or parameters

All in a single file, making translations accurate and contextually appropriate.


CLI and Missing Key Detection

Unused Translation Detection

The package includes a CLI tool that scans your codebase for unused translation keys:

# Check for unused translations
npx react-typesafe-translations analyze

# Specify a different directory
npx react-typesafe-translations analyze --cwd ./my-app

# Add to package.json scripts
{
  "scripts": {
    "lint:translations": "react-typesafe-translations analyze"
  }
}

The analyzer:

  • Detects translation keys that are defined but never used
  • Follows destructuring and variable renaming
  • Tracks usage across files via imports
  • Resolves shared/global wrapper hooks (e.g. useGlobalTranslations()) back to their underlying useTranslations() call, so usage routed through a wrapper is still counted
  • Warns about dynamic access patterns that defeat type safety
  • Exits with code 1 if unused keys are found (perfect for CI)

Example output:

src/components/Header.tsx:
  12:3  warning  Translation key 'oldTitle' is defined but never used
  15:3  warning  Translation key 'deprecatedLabel' is defined but never used

✖ 2 unused translation keys found

VS Code Extension

Install the companion VS Code extension for real-time feedback.

The VS Code extension provides:

  • Real-time warnings for unused translation keys as you type
  • Inline diagnostics directly in the editor
  • Zero configuration — works automatically with useTranslations()
  • Smart analysis — handles complex destructuring patterns

Make sure VS Code is using your workspace TypeScript version:

  1. Open Command Palette (Cmd+Shift+P / Ctrl+Shift+P)
  2. Run "TypeScript: Select TypeScript Version"
  3. Choose "Use Workspace Version"

Automatic Code Splitting

Because translations are co-located with components, you get automatic lazy loading of translations when components are lazy-loaded.

Traditional i18n: Monolithic Translation Files

In traditional i18n setups, all translations live in large JSON files:

└── locales/
    ├── en.json  (500 KB - loads everything upfront)
    └── fi.json  (500 KB - loads everything upfront)

Every translation for every component loads immediately, even for routes the user never visits.

react-typesafe-translations: Automatic Splitting

With co-located translations, your bundle naturally splits:

// routes.tsx
import { lazy } from 'react';

const AdminPanel = lazy(() => import('./AdminPanel'));
const UserDashboard = lazy(() => import('./UserDashboard'));

When AdminPanel lazy loads, its translations load too — automatically:

// AdminPanel.tsx
import { useTranslations } from '~/utils/i18n';

const translations = {
  title: { fi: 'Hallintapaneeli', en: 'Admin Panel' },
  // ... more admin-specific translations
} satisfies Translations;

export function AdminPanel() {
  const { t } = useTranslations(translations);
  // ... component code
}

Result: Users only download translations for the routes they actually visit.

Benefits

  • Smaller initial bundles — Only load translations for the initial route
  • Faster page loads — Reduce time-to-interactive
  • No configuration needed — Works automatically with React's lazy()
  • Scales naturally — As your app grows, translations split automatically
  • Better caching — Each route's translations can be cached independently

This is especially impactful for large apps with many routes, where traditional i18n would load hundreds of kilobytes of unused translations upfront.


API Reference

createTranslationsFactory<AllLanguages, RequiredLanguages>(baseLanguage)

Creates the i18n factory.

  • AllLanguages: all allowed locales (e.g. 'en' | 'fi')
  • RequiredLanguages: subset of AllLanguages that must have translations defined
  • baseLanguage: used as fallback, must be one of RequiredLanguages

Returns:

  • useTranslations(translations)
  • setLanguage(lang)
  • useLanguage()
  • Translations type helper

Translation Objects

const translations = {
  simple: {
    fi: 'Hei',
    en: 'Hello',
  },
  paramExample: {
    fi: (name: string) => `Moi, ${name}`,
    en: (name: string) => `Hi, ${name}`,
  },
} satisfies Translations;

Languages listed in RequiredLanguages must have a translation. Other languages in AllLanguages may explicitly be set to undefined to indicate intentionally missing translations. You are not allowed to omit translations completely for any language you've defined in AllLanguages.


Multiple & Shared Translation Sets

You can define separate translation groups per component or domain:

const labels = {
  save: { fi: 'Tallenna', en: 'Save' },
  cancel: { fi: 'Peruuta', en: 'Cancel' },
} satisfies Translations;

const labels2 = {
  loading: { fi: 'Ladataan', en: 'Loading...' },
} satisfies Translations;

You can use multiple useTranslations() calls in one component if needed.

Shared / global hook

For a set of translations used app-wide, you can wrap a single root object in your own hook:

// useGlobalTranslations.ts
import { translations } from './translations';
export const useGlobalTranslations = () => useTranslations(translations);
// any component
const { t } = useGlobalTranslations();
return <span>{t.welcome}</span>;

The unused-key analyzer understands this pattern: it resolves the wrapper hook (including wrappers that call other wrappers) back to the underlying useTranslations() call, so usage is attributed to the shared object across every component that consumes it. Renamed destructuring (const { t: tr } = useGlobalTranslations()) is supported too.


Comparison

react-typesafe-translations vs react-i18next vs typesafe-i18n

| Feature | react-typesafe-translations | react-i18next | typesafe-i18n | | ----------------------------- | ---------- | ------------- | ------------- | | Type-safe keys | ✅ | ❌ | ✅ | | Function param safety | ✅ | ❌ | ✅ | | Autocomplete translations | ✅ | ❌ | ✅ | | Jump to definition | ✅ | ❌ | ✅ | | satisfies-based validation | ✅ | ❌ | ❌ | | Build step required | ❌ | ❌ | ✅ | | External translation files | ❌ | ✅ | ✅ | | ICU message syntax | ❌ | ✅ | ✅ | | Per-component co-location | ✅ | ❌ | ❌ | | LLM-friendly architecture | ✅ | ❌ | ❌ | | Automatic code splitting | ✅ | ❌ | ❌ | | Unused key detection | ✅ | ❌ | ❌ | | Runtime performance | ✅ | Medium | ✅ | | Bundle size | Minimal | Medium–Large | Small | | Ease of setup | ✅ | ❌ | ❌ |

⚠️ While react-i18next has TypeScript types, it does not enforce key or param safety, nor provide strong IDE support out of the box.

Summary

  • react-typesafe-translations is ideal for apps maintained by developers and AI assistants, with translations co-located and inline, no build tools, full TS safety, and automatic code splitting. Perfect for teams leveraging LLM coding assistants.
  • react-i18next is better for content-managed apps or translator-facing tools, but lacks TS integration and requires manual bundle optimization.
  • typesafe-i18n is also very type-safe, but requires codegen and centralized translation files. More scalable but less nimble. typesafe-i18n also has not been updated in a while, so it may not support the latest TypeScript features or React versions.

Why No ICU?

react-typesafe-translations does not support ICU message syntax (e.g. {count, plural, one {# item} other {# items}}) — intentionally.

ICU-based formats rely on magic string parsing and brittle runtime logic. Instead, react-typesafe-translations encourages writing actual JavaScript functions for conditionals, plurals, and logic. This keeps translations maintainable, testable, and fully type-safe.

If you prefer ICU for translator-facing tooling, consider using react-i18next or typesafe-i18n instead.


Best Practices

  • Always use satisfies Translations
  • Keep translations close to usage
  • Keep base language values simple and complete
  • Catch translation errors in CI by running typechecks
  • Prefer small translation objects per component or domain

Intended Use Cases

react-typesafe-translations is ideal for:

  • Small to medium React apps
  • Projects with 2–5 locales
  • Developer-maintained translations
  • Teams using AI coding assistants (GitHub Copilot, Cursor, etc.)
  • Apps that benefit from code splitting and lazy loading
  • High confidence in type safety and DX

When Not to Use This

  • Apps with 10+ languages (maintenance overhead grows linearly)
  • Translator-facing tools (no JSON/XLIFF support)
  • Apps needing dynamic runtime translations (e.g., CMS-driven)

License

MIT