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

tyndale-react

v1.1.1

Published

React components and hooks for Tyndale i18n

Readme

tyndale-react

npm version Build Status TypeScript License

React runtime for Tyndale — components and hooks that render translations in your React app with no configuration overhead.

Overview

tyndale-react is the client-side half of Tyndale's i18n system. It provides the <T> component for translating JSX, hooks for translating plain strings, and locale-aware formatting components for numbers, currencies, and dates.

The CLI (tyndale) extracts source strings from these components, sends them to an AI for translation, and writes locale JSON files that this runtime loads at startup.

[!NOTE] If you are using Next.js, also install tyndale-next for locale routing, middleware, and server-side rendering support.

Installation

npm install tyndale-react
# or
bun add tyndale-react

Peer dependency: React 18 or 19.

Quickstart

1. Wrap your app in TyndaleProvider

import { TyndaleProvider } from 'tyndale-react';

export function App() {
  return (
    <TyndaleProvider defaultLocale="en" locale="es">
      <YourApp />
    </TyndaleProvider>
  );
}

The provider fetches /_tyndale/{locale}.json and /_tyndale/manifest.json automatically. While loading, it renders children as-is using the source content — no flash, no empty state.

2. Mark translatable JSX with <T>

import { T, Var, Num } from 'tyndale-react';

export function Welcome({ name, count }: { name: string; count: number }) {
  return (
    <T>
      <h1>Hello, <Var name="user">{name}</Var>!</h1>
      <p>You have <Num value={count} /> unread messages.</p>
    </T>
  );
}

The CLI extractor recognizes <T> blocks and sends their content for translation. At runtime, <T> hashes the serialized content, looks up the translation, and re-renders it with the original React elements and variable bindings restored.

3. Translate plain strings with useTranslation()

import { useTranslation } from 'tyndale-react';

export function SearchBar() {
  const t = useTranslation();

  return <input placeholder={t('Search products...')} />;
}

Supports {name} interpolation:

const message = t('Welcome back, {name}!', { name: user.displayName });

4. Generate translations

npx tyndale translate

This extracts all <T> blocks and useTranslation() calls, sends changed strings to the AI, and writes locale files to public/_tyndale/.

Components

<T>

Wraps translatable JSX. The CLI extracts the serialized content; the runtime looks up the translation by content hash and re-renders it.

<T>
  <p>This entire paragraph is translatable.</p>
</T>

Falls back to source children when no translation is found or when the provider is not mounted.


<Var name="...">

A named dynamic slot inside <T>. The CLI preserves it as a {name} placeholder in the translation string, and the runtime substitutes the original element back at render time.

<T>
  <p>Hello, <Var name="user">{userName}</Var>!</p>
</T>

<Num>

Locale-aware number formatter. Wraps Intl.NumberFormat.

<Num value={1234567} />
// → "1,234,567" (en) / "1.234.567" (de)

<Num value={0.42} options={{ style: 'percent' }} />
// → "42%"

Inside <T>, serialized as a named placeholder; outside <T>, renders the formatted number directly.


<Currency>

Locale-aware currency formatter.

<Currency value={29.99} currency="USD" />
// → "$29.99" (en-US) / "29,99 $" (fr-FR)

<DateTime>

Locale-aware date/time formatter. Accepts a Date, Unix timestamp, or ISO string.

<DateTime value={new Date()} options={{ dateStyle: 'long' }} />
// → "April 16, 2026" (en) / "16 avril 2026" (fr)

<Plural>

CLDR-based plural selection. Supports all six plural categories (zero, one, two, few, many, other).

<Plural count={itemCount} one="{count} item" other="{count} items" />

{count} in each branch is interpolated with the actual count. Inside <T>, serialized to ICU plural format.

Hooks and functions

useTranslation()

Returns a t(source, vars?) function for translating plain strings.

const t = useTranslation();
const label = t('Save changes');
const greeting = t('Hello, {name}!', { name: 'Alice' });

Hashes the source string, looks up the translation, applies interpolation, and falls back to the source when no translation exists.


useLocale()

Returns the current locale string.

const locale = useLocale(); // "es"

useChangeLocale()

Returns a function that fetches a new locale and updates the provider. Uses last-write-wins semantics: concurrent calls abort in-flight fetches.

const changeLocale = useChangeLocale();
<button onClick={() => changeLocale('fr')}>Français</button>

useDictionary(filenameKey)

Resolves dictionary entries for a given filename key. Dictionaries are JSON key-value files translated by the CLI alongside JSX strings.

// Assuming src/dictionaries/nav.json was translated
const nav = useDictionary('nav');
// → { "home": "Inicio", "about": "Acerca de" }

msg(source)

Marks a translatable string defined outside a component's render function. Returns a React element that resolves to the translated string at render time inside a TyndaleProvider.

const NAV_ITEMS = [
  { label: msg('Home'), href: '/' },
  { label: msg('About'), href: '/about' },
];

// In JSX:
<a href={item.href}>{item.label}</a>

The CLI extractor recognizes msg('literal') calls and extracts the argument.


msgString(source)

Like msg(), but for non-React contexts (Astro, Node.js) where a plain string is needed. Returns the source string unchanged at runtime; the CLI extractor still picks it up.

import { msgString } from 'tyndale-react';
const title = msgString('Page title');

getTranslation(options) — server entry (tyndale-react/server)

Async server-side translation function. Loads locale files from disk and returns a t() function.

import { getTranslation } from 'tyndale-react/server';

const t = await getTranslation({
  locale: 'fr',
  defaultLocale: 'en',
  outputPath: './public/_tyndale',
});

const title = t('Welcome');

[!NOTE] For Next.js server components, use TyndaleServerProvider from tyndale-next instead — it handles file loading and passes translations through React context automatically.

TyndaleProvider props

| Prop | Type | Default | Description | |---|---|---|---| | defaultLocale | string | — | Source locale the app is written in | | locale | string | defaultLocale | Active locale to display | | basePath | string | '/_tyndale' | Base URL for locale JSON files | | initialTranslations | Record<string, string> | — | Pre-loaded translations (skips fetch; use for SSR or testing) | | initialManifest | Manifest \| null | — | Pre-loaded manifest | | onLocaleChange | (locale: string) => void | — | Called when locale changes (controlled mode) |

How it works

Tyndale uses a content-addressed translation store. When you write:

<T><p>Hello, <Var name="user">{name}</Var>!</p></T>

The CLI serializes the JSX structure into a wire format string and computes its SHA-256 hash. The translated string is stored at that hash in the locale JSON file. At runtime, <T> performs the same serialization and hash, looks up the translated wire format, then deserializes it back into React elements — restoring <Var>, <Num>, and other components with their original props.

This means:

  • No string IDs to manage — the source content is the key.
  • Structural changes (adding a word, wrapping in a tag) produce a new hash and trigger re-translation automatically.
  • Translations are lazy: only the active locale file is loaded.

Related packages

| Package | Purpose | |---|---| | tyndale | CLI — init, extract, translate, validate, and more | | tyndale-next | Next.js adapter — middleware, server provider, static generation |