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

@vocoder/react

v0.16.2

Published

React components for internationalization (i18n) with SSR support, ICU MessageFormat, and build-time translation generation

Readme

@vocoder/react

React components and hooks for the Vocoder i18n platform. Provides <T> for translating JSX, t() for plain strings, and a provider that manages locale state with SSR support.

Installation

npm install @vocoder/react

Requires React 18+. Pair with @vocoder/plugin to enable build-time extraction and translation loading.


Setup

SPA (Vite, Create React App, etc.)

Call initializeVocoder() before the first render to avoid a flash of untranslated content, then wrap your app:

// main.tsx
import ReactDOM from 'react-dom/client';
import { initializeVocoder, VocoderProvider } from '@vocoder/react';
import { App } from './App';

async function bootstrap() {
  await initializeVocoder();
  ReactDOM.createRoot(document.getElementById('root')!).render(
    <VocoderProvider>
      <App />
    </VocoderProvider>,
  );
}

bootstrap();

SSR (Next.js App Router)

Pass the request cookies so the server renders the correct locale on the first byte:

// app/layout.tsx
import { cookies } from 'next/headers';
import { VocoderProvider } from '@vocoder/react';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const cookieStore = await cookies();
  return (
    <html>
      <body>
        <VocoderProvider cookies={cookieStore.toString()}>
          {children}
        </VocoderProvider>
      </body>
    </html>
  );
}

The provider injects a hydration snapshot so the client renders the correct locale on first paint without a flash.

VocoderProvider props

| Prop | Type | Default | Description | |---|---|---|---| | children | ReactNode | required | Your app tree | | cookies | string | — | Cookie string from the request (SSR only) | | applyDir | boolean | true | Automatically set dir and lang on document.documentElement when locale changes. Enables RTL via CSS ([dir="rtl"], Tailwind rtl: variants). |

Locale persistence

The active locale is stored in localStorage and a vocoder_locale cookie. On the server, the cookie is read from the cookies prop.


The <T> Component

<T> handles all translation modes: plain text, interpolation, plurals, selects, ordinals, rich text, and locale-aware formatting.

Natural JSX syntax

Write your content directly as JSX children. @vocoder/plugin injects the message and values props automatically at build time — no manual string management required:

import { T } from '@vocoder/react';

// Static text
<T>Hello, world!</T>

// Variables — the build plugin injects: message="Hello {name}!" values={{ name }}
<T>Hello {name}!</T>

// JSX children with components — plugin injects message and components prop
<T>Read <a href="/docs" className="underline">the docs</a> for help.</T>

Explicit message prop

Use message directly when you need full control over the ICU string:

<T message="Hello, {name}!" values={{ name: user.name }} />
<T message="{count, plural, one {# item} other {# items}}" values={{ count }} />

Pluralization

Use CLDR plural category props alongside value:

// Cardinal plural
<T value={count} one="# item" other="# items" />

// With zero exact match
<T value={count} _0="No items" one="# item" other="# items" />

// All CLDR categories (for Polish, Arabic, etc.)
<T value={count} one="# przedmiot" few="# przedmioty" many="# przedmiotów" other="# przedmiotu" />

Exact numeric matches use underscore-prefixed numbers (_0, _1, _2). They map to ICU =0, =1, =2.

CLDR categories: zero, one, two, few, many, other. Which categories are active depends on the locale — Vocoder handles this automatically.

Select (gender, status, etc.)

Use underscore-prefixed word props alongside value:

// Gender-based select
<T value={gender} _male="He replied" _female="She replied" other="They replied" />

// Status select
<T value={status} _pending="Awaiting review" _approved="Approved" other="Unknown" />

Ordinals

Rank numbers in the active locale's ordinal style (1st, 2nd, 3rd; 1.º, 2.º; etc.):

<T value={rank} ordinal />
// en → "1st", "2nd", "3rd"
// es → "1.º", "2.º"
// fr → "1er", "2e"

// Word-based ordinals (Arabic, Hebrew) — pass gender for correct inflection
<T value={rank} ordinal gender="feminine" />

Ordinal forms are defined per locale in the manifest config generated by the CLI.

Rich text

Wrap inline elements with numeric component placeholders. The build plugin injects these automatically when you use natural JSX syntax.

Array form (sequential index, most common):

// Single link
<T
  message="Click <0>here</0> to learn more"
  components={[<a href="/help" />]}
/>

// Multiple components — index matches order in the array
<T
  message="Read our <0>Privacy Policy</0> and <1>Terms of Service</1>"
  components={[
    <a href="/privacy" />,
    <a href="/terms" />,
  ]}
/>

Object form (sparse indices, useful when skipping slots):

<T
  message="<0>Bold</0> and <2>italic</2> text"
  components={{
    0: <strong />,
    2: <em />,
  }}
/>

Function slots — receive translated inner content as ReactNode, return ReactNode. Use when the wrapper element needs dynamic props derived at render time:

<T
  message="Terms: <0>I agree</0> to the policy"
  components={[(children) => (
    <label className="flex items-center gap-1">
      <input type="checkbox" />
      <span>{children}</span>
    </label>
  )]}
/>

Self-closing components (icons, images):

<T
  message="Upload complete <0/>"
  components={[<CheckIcon className="inline w-4 h-4" />]}
/>

Nested components:

<T
  message="See <0>our <1>docs</1></0> for details"
  components={[<a href="/docs" />, <strong />]}
/>

React elements in values are auto-promoted to self-closing component slots — no components prop needed:

<T
  message="Rating: {star} — highly recommended"
  values={{ star: <StarIcon className="inline w-4 h-4 text-yellow-500" /> }}
/>

Locale-aware formatting

Use the format prop to format numbers, currencies, and dates without a translation lookup. The value is formatted using Intl APIs for the active locale.

// Numbers
<T value={1234.56} format="number" />      // "1,234.56" (en), "1.234,56" (de)
<T value={1234.56} format="integer" />     // "1,235"
<T value={0.742} format="percent" />       // "74.2%"
<T value={1234567} format="compact" />     // "1.2M"

// Currency — requires the currency prop (ISO 4217)
<T value={29.99} format="currency" currency="USD" />   // "$29.99"
<T value={29.99} format="currency" currency="EUR" />   // "€29,99" (fr)

// Dates
<T value={new Date()} format="date" dateStyle="long" />      // "May 6, 2025"
<T value={new Date()} format="time" timeStyle="short" />     // "3:45 PM"
<T value={new Date()} format="datetime" dateStyle="medium" timeStyle="short" />

Format modes:

| format | Description | Relevant props | |---|---|---| | number | Locale decimal number | — | | integer | Rounded integer | — | | percent | Percentage | — | | compact | Compact notation (1.2M, 4.5K) | — | | currency | Currency symbol + amount | currency (required) | | date | Date only | dateStyle (full / long / medium / short, default medium) | | time | Time only | timeStyle (full / long / medium / short, default short) | | datetime | Date and time | dateStyle, timeStyle |


Context and formality

Use context to disambiguate identical source strings with different meanings:

<T context="button">Save</T>
<T context="noun">Save</T>
// Same source text, different translations, different catalog keys

Use formality to hint at the required register for the translator:

<T formality="formal">Please submit your application.</T>
<T formality="informal">Go ahead and apply!</T>

Use id to supply a stable catalog key that survives source text edits:

<T id="onboarding.welcome">Welcome to the app!</T>

Props reference

| Prop | Type | Description | |---|---|---| | children | ReactNode | Source text / fallback content. Also the translation input when no message prop is present. | | message | string | ICU message string. Takes precedence over children for lookup. | | values | Record<string, any> | Runtime values for {name} interpolation. The only supported way to pass variables. | | id | string | Stable catalog key — bypasses content hashing. | | context | string | Disambiguation string. Same source text + different context = different catalog entry. | | formality | 'formal' \| 'informal' \| 'auto' | Translation register hint. | | components | ComponentSlot[] \| Record<number, ComponentSlot> | Component slots for <0>, <1> rich-text placeholders. Each slot is a ReactElement or (children: ReactNode) => ReactNode. | | value | string \| number \| Date | The value driving plural/select/ordinal selection, or the input to format. | | one two few many other | string | CLDR plural branches. Activates plural mode when present alongside value. Use # as the number placeholder. | | _0 _1 _2 | string | Exact numeric matches in plural mode (ICU =0, =1, =2). | | _male _female _nonbinary … | string | Select cases. Activates select mode when present without CLDR props. Key after _ becomes the ICU case. | | ordinal | boolean | Switches to ordinal mode. Formats value as a locale-aware ordinal (1st, 2nd, …). | | gender | string | Grammatical gender for word-based ordinal locales (Arabic, Hebrew). | | format | FormatMode | Pure Intl formatting — bypasses translation lookup. | | currency | string | ISO 4217 code required when format="currency". | | dateStyle | 'full' \| 'long' \| 'medium' \| 'short' | Date display style. Default 'medium'. | | timeStyle | 'full' \| 'long' \| 'medium' \| 'short' | Time display style. Default 'short'. |


The t() Function

Use t() for translations outside JSX — toast messages, aria-label, document.title, validation errors, etc.

import { t } from '@vocoder/react';

// Simple
const label = t('Hello, world!');

// With variables
const greeting = t('Hello, {name}!', { name: user.name });

// ICU plural
const summary = t('{count, plural, =0 {No items} one {# item} other {# items}}', { count });

// Context disambiguation
const action = t('Save', {}, { context: 'button' });

// Explicit catalog key
const banner = t('', {}, { id: 'welcome_banner' });

t() uses global state synced by VocoderProvider. Call it only after the provider has mounted. Rich text with component slots is not supported — use <T> for that.

Options

| Option | Type | Description | |---|---|---| | context | string | Must match the context on the corresponding <T> | | formality | 'formal' \| 'informal' \| 'auto' | Translation register hint | | id | string | Stable lookup key — skips hashing the source text |


The ordinal() Function

Format a number as a locale-aware ordinal outside of React components:

import { ordinal } from '@vocoder/react';

ordinal(1)   // "1st" (en), "1.º" (es), "1er" (fr)
ordinal(2)   // "2nd" (en), "2.º" (es), "2e" (fr)
ordinal(21)  // "21st" (en), "21.º" (es)

// Word-based locales (Arabic, Hebrew)
ordinal(1, 'feminine')
ordinal(1, 'masculine')

Reads ordinal forms from the manifest config for the current locale. Falls back to String(value) when forms are unavailable.


The useVocoder() Hook

Access locale state and translation utilities in components. Reactive — re-renders when the locale changes.

import { useVocoder } from '@vocoder/react';

function LocaleSwitcher() {
  const {
    locale,           // Current locale code: 'en', 'es', 'fr', …
    setLocale,        // (locale: string) => Promise<void>
    availableLocales, // string[] — all configured locale codes
    locales,          // LocalesMap — metadata (nativeName, dir, currencyCode, ordinalForms)
    isReady,          // true when initial translations are loaded
    dir,              // 'ltr' | 'rtl' — text direction for the active locale
    t,                // Reactive translate function — use inside components
    hasTranslation,   // (text: string) => boolean
    getDisplayName,   // (targetLocale: string, viewingLocale?: string) => string
    ordinal,          // (value: number, gender?: string) => string
  } = useVocoder();

  return (
    <select value={locale} onChange={(e) => setLocale(e.target.value)}>
      {availableLocales.map((code) => (
        <option key={code} value={code}>
          {getDisplayName(code)} ({locales?.[code]?.nativeName})
        </option>
      ))}
    </select>
  );
}

t vs global t(): useVocoder().t re-renders automatically when the locale changes and is safe to call during render. The global t() export does not subscribe to React and should be used in callbacks, utilities, and non-React contexts.


Locale Selector

A pre-built floating locale switcher, shipped as a separate entry point to avoid bundling Radix UI unless you need it:

import { LocaleSelector } from '@vocoder/react/locale-selector';

// Floating bottom-right selector
<LocaleSelector position="bottom-right" />

// Custom appearance
<LocaleSelector
  position="top-right"
  background="#1a1a1a"
  color="#ffffff"
  iconSize={20}
  sortBy="native"
/>

Requires peer dependencies:

npm install @radix-ui/react-dropdown-menu lucide-react

Props

| Prop | Type | Default | Description | |---|---|---|---| | position | 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' (or aliases tl tr bl br) | — | Fixed position on the screen | | background | string | light-dark(#1a1a1a, #EFEAE3) | Button and dropdown background color | | color | string | light-dark(#EFEAE3, #1a1a1a) | Button and dropdown text/icon color | | className | string | — | Additional CSS class on the root element | | iconSize | number | — | Globe icon size in pixels | | locales | LocalesMap | — | Override locale metadata (auto-generated by CLI if omitted) | | sortBy | 'source' \| 'native' \| 'translated' | 'native' | Sort order: by English names, native names, or names translated into the viewing locale |


Server utilities (@vocoder/react/server)

getLocaleDir(locale, locales)

Returns the text direction for a locale using the metadata from the Vocoder manifest. Use this in Next.js App Router layouts to set dir on the <html> tag before the client hydrates — VocoderProvider handles dir on the client, but the server render needs it independently.

// app/layout.tsx
import { cookies } from 'next/headers';
import { getConfig, getLocales, VocoderProvider } from '@vocoder/react';
import { getLocaleDir } from '@vocoder/react/server';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const cookieStore = await cookies();
  const { sourceLocale } = getConfig();
  const locale = cookieStore.get('vocoder_locale')?.value ?? sourceLocale;
  const dir = getLocaleDir(locale, getLocales());
  return (
    <html lang={locale} dir={dir}>
      <body>
        <VocoderProvider initialLocale={locale} preview={cookieStore.get('vocoder_preview')?.value === 'true'}>
          {children}
        </VocoderProvider>
      </body>
    </html>
  );
}

| Parameter | Type | Description | |---|---|---| | locale | string | The locale code to look up | | locales | Record<string, { dir?: string }> | Locale metadata map — pass config.locales from the virtual manifest |

Returns 'rtl' when the locale metadata has dir: 'rtl', otherwise 'ltr'.


Preview mode

Preview mode lets you ship Vocoder to production but keep it inactive by default. It is opt-in: only visitors who explicitly enable it see translated content. This is useful for QA, stakeholder review, or staged rollouts before a full translation launch.

How it works

@vocoder/plugin accepts a preview option. When preview: true, the build constant __VOCODER_PREVIEW__ is set to true, which flips PREVIEW_MODE at runtime. In preview mode the SDK is only active for users who have the vocoder_preview=true cookie set.

The ?vocoder_preview=true URL parameter sets that cookie and then strips itself from the URL. VocoderProvider handles this automatically — you do not call syncPreviewQueryParam directly.

Exports

PREVIEW_MODE, isPreviewEnabled, and isVocoderEnabled are exported from @vocoder/react:

import { PREVIEW_MODE, isPreviewEnabled, isVocoderEnabled } from '@vocoder/react';

| Export | Type | Description | |---|---|---| | PREVIEW_MODE | boolean | true when the build was compiled with preview: true in the plugin config. Compile-time constant — false in normal production builds. | | isPreviewEnabled(cookieString?) | (string?) => boolean | true when the visitor has opted in via the vocoder_preview=true cookie. Pass the raw cookie string for server-side calls. | | isVocoderEnabled(cookieString?) | (string?) => boolean | true when the SDK should be active — either PREVIEW_MODE is false (standard build), or the visitor has opted in. Use this to gate SSR translation logic. |

Gating SSR translation in Next.js

// app/layout.tsx
import { cookies } from 'next/headers';
import { isVocoderEnabled } from '@vocoder/react';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const cookieStore = await cookies();
  const cookieString = cookieStore.toString();

  if (!isVocoderEnabled(cookieString)) {
    // Vocoder is in preview mode and this visitor hasn't opted in — render without translations
    return <html><body>{children}</body></html>;
  }

  // Normal SSR path with locale detection
  // ...
}

Enabling preview for a visitor

Append ?vocoder_preview=true to any URL in the app. VocoderProvider reads the param on mount, writes the cookie, and redirects to the clean URL. To disable, append ?vocoder_preview=false.


generateMessageHash(text, context?, formality?)

Computes the same 7-character catalog key that the build plugin and extractor generate at compile time. Use this in custom tooling — import scripts, catalog validators, or test fixtures — when you need to construct or look up a message key outside the normal build pipeline.

import { generateMessageHash } from '@vocoder/react';

generateMessageHash('Hello, world!')               // → e.g. "3j8kq2a"
generateMessageHash('Save', 'button')              // → different key from "Save" alone
generateMessageHash('Save', 'noun')                // → different again
generateMessageHash('Submit', undefined, 'formal') // → separate key from informal

| Parameter | Type | Description | |---|---|---| | text | string | The source message text | | context | string (optional) | Disambiguation context — must match the context prop on <T> | | formality | string (optional) | 'formal' or 'informal'. Any other value (including 'auto' and undefined) hashes identically. |

Returns a 7-character base-36 string. The algorithm is FNV-1a 32-bit, guaranteed identical between Node.js and browsers — the extractor and the runtime always produce the same key for the same inputs.


Extractor: what gets extracted

@vocoder/plugin transforms <T> components at build time to inject message, values, and components props. Understanding what the extractor supports helps you write translatable code correctly.

What works

| Child expression | Extracted as | |---|---| | Plain text | Literal text | | {name} (identifier) | {name} ICU placeholder | | `Hello ${name}` (template literal) | Hello {name} | | {user.name} {getLabel()} (complex) | {0} positional placeholder; value injected automatically | | {42} (numeric literal) | "42" inlined as text | | <a href="/docs">text</a> (JSX element) | <0>text</0> component slot |

What bails (T is not transformed — warn emitted)

| Pattern | Problem | Correct alternative | |---|---|---| | <T>{isNew ? 'New' : 'Old'} item</T> | Conditional produces different strings — no stable catalog key | {isNew ? <T>New item</T> : <T>Old item</T>} | | <T>Status: {flag && 'visible'}</T> | Logical expression — not a stable unit | <T>Status:</T> {flag && <T>visible</T>} | | <T>Hello <T>world</T></T> | Nested <T> — outer bails; inner extracts independently | <T>Hello</T> <T>world</T> or use a component slot for styled content |

Skipped without extraction

| Expression | Reason | |---|---| | {true} {false} {null} | React renders nothing — no translation content |


How it works

Build-time bundle injection

Translations are delivered via __VOCODER_BUNDLE__ — a compile-time constant injected by @vocoder/plugin. The full VocoderTranslationData (config + all locale strings) is inlined as a single JSON literal at build time. No virtual modules, no runtime fetches required for client bundles.

On SSR (Node.js), if __VOCODER_BUNDLE__ is unavailable, the runtime falls back to reading node_modules/.vocoder/cache/{fingerprint}.json from disk.

Background refresh

After the initial render, the provider compares the bundle's build timestamp against the Vocoder API. If newer translations exist they are applied in-memory without a page reload — no redeploy required for translation-only updates.

Translation key format

Each message is identified by a 7-character FNV-1a 32-bit hash of the source text (plus context when provided). The build plugin injects these hashes as id props at compile time, keeping the network payload small.


TypeScript

All types are exported from @vocoder/react:

import type {
  ComponentSlot,       // ReactElement | ((children: ReactNode) => ReactNode)
  FormatMode,          // 'number' | 'integer' | 'percent' | 'compact' | 'currency' | 'date' | 'time' | 'datetime'
  LocaleInfo,          // { nativeName, dir?, currencyCode?, ordinalForms? }
  LocaleSelectorProps,
  LocalesMap,          // Record<string, LocaleInfo>
  TOptions,            // { context?, formality?, id? }
  TProps,
  TranslationsMap,
  VocoderContextValue,
  VocoderProviderProps,
} from '@vocoder/react';

// Runtime values (not types)
import {
  generateMessageHash, // (text, context?, formality?) => string — 7-char catalog key
  PREVIEW_MODE,        // boolean — compile-time constant, true when built with preview: true
  isPreviewEnabled,    // (cookieString?) => boolean
  isVocoderEnabled,    // (cookieString?) => boolean
} from '@vocoder/react';

// Server-only utilities
import { getLocaleDir } from '@vocoder/react/server';
import type { VocoderProviderServerProps } from '@vocoder/react/server';

License

MIT