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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@polingo/react

v0.0.3

Published

React bindings for the Polingo translation engine

Readme

@polingo/react

npm version npm bundle size

React bindings for the Polingo translation engine.

This package exposes a context provider, data hooks, and a rich-text <Trans /> component that wrap the framework-agnostic Translator from @polingo/core.

Contents

  1. Installation
  2. Quick start
  3. Step-by-step setup guide
  4. Translation workflow
  5. API reference
  6. Advanced patterns
  7. Related packages
  8. License

Installation

npm install @polingo/core @polingo/web @polingo/react
# or
pnpm add @polingo/core @polingo/web @polingo/react
# or
yarn add @polingo/core @polingo/web @polingo/react

For development, you'll also want the CLI tooling:

npm install -D @polingo/cli
# or
pnpm add -D @polingo/cli

Quick start

import { PolingoProvider, Trans, useTranslation } from '@polingo/react';

function Example(): JSX.Element {
  const { t, tn, setLocale, locale } = useTranslation();

  return (
    <div>
      <p>{t('Hello {name}', { name: 'Polingo' })}</p>
      <p>{tn('You have {n} message', 'You have {n} messages', 3)}</p>

      <button type="button" onClick={() => setLocale(locale === 'en' ? 'es' : 'en')}>
        <Trans message="Switch to <0>Spanish</0>" components={[<strong />]} />
      </button>
    </div>
  );
}

export function App(): JSX.Element {
  return (
    <PolingoProvider
      create={{
        locale: 'en',
        locales: ['en', 'es'],
        loader: { baseUrl: '/i18n' },
      }}
    >
      <Example />
    </PolingoProvider>
  );
}

Step-by-step setup guide

1. Create your project structure

First, create a directory structure for your locale files. The recommended structure is:

your-app/
├── public/
│   └── i18n/              # Translation catalogs for web loader
│       ├── en/
│       │   └── messages.json
│       └── es/
│           └── messages.json
├── locales/               # Source .po files
│   ├── messages.pot       # Template (generated by CLI)
│   ├── en/
│   │   └── messages.po
│   └── es/
│       └── messages.po
└── src/
    ├── App.tsx
    └── components/

2. Install dependencies

Install the required packages:

pnpm add @polingo/core @polingo/web @polingo/react
pnpm add -D @polingo/cli

3. Wrap your app with PolingoProvider

Update your root component (e.g., src/App.tsx or src/main.tsx):

import { PolingoProvider } from '@polingo/react';

export function App() {
  return (
    <PolingoProvider
      create={{
        locale: 'en', // Default locale
        locales: ['en', 'es', 'fr'], // All supported locales
        loader: {
          baseUrl: '/i18n', // URL where catalogs are served
        },
        cache: true, // Enable localStorage caching
        cacheOptions: {
          prefix: 'my-app', // Namespace for cache keys
          ttlMs: 86_400_000, // 24 hours cache lifetime
        },
      }}
    >
      <YourApp />
    </PolingoProvider>
  );
}

4. Use translations in your components

Use the useTranslation hook to access translation methods:

import { useTranslation } from '@polingo/react';

function MyComponent() {
  const { t, tp, tn, tnp, locale, setLocale } = useTranslation();

  return (
    <div>
      {/* Basic translation */}
      <h1>{t('Welcome')}</h1>

      {/* Translation with variables */}
      <p>{t('Hello {name}!', { name: 'Alice' })}</p>

      {/* Translation with context (disambiguates homonyms) */}
      <button>{tp('menu', 'File')}</button>

      {/* Pluralization */}
      <p>{tn('You have {n} message', 'You have {n} messages', count, { n: count })}</p>

      {/* Pluralization with context */}
      <p>{tnp('inbox', '{n} new email', '{n} new emails', count, { n: count })}</p>

      {/* Switch locale */}
      <button onClick={() => setLocale('es')}>Español</button>
      <button onClick={() => setLocale('en')}>English</button>
    </div>
  );
}

5. Use the Trans component for rich-text translations

The Trans component allows you to embed React components inside translations:

import { Trans } from '@polingo/react';

function RichTextExample() {
  return (
    <div>
      {/* Embed components with <0>, <1>, etc. placeholders */}
      <Trans
        message="I agree to the <0>terms of service</0> and <1>privacy policy</1>"
        components={[<a href="/terms" />, <a href="/privacy" />]}
      />

      {/* Combine with variables */}
      <Trans message="Welcome <0>{name}</0>!" components={[<strong />]} vars={{ name: 'Alice' }} />
    </div>
  );
}

6. Extract translatable strings from your code

Use the CLI to scan your source code and update your locale catalogs:

# Extract from src, sync locale catalogs, and auto-clean the temporary POT template
pnpm polingo extract src --locales locales --languages en,es --default-locale en

# Or use npx
npx @polingo/cli extract src --locales locales --languages en,es --default-locale en

This command finds all calls to t(), tp(), tn(), tnp(), and <Trans> in your codebase and updates (or creates) locales/<lang>/messages.po for every locale you list. The default locale copies the source strings into msgstr, while other locales get empty placeholders ready for translators. Add --keep-template if you need the generated locales/messages.pot for manual review.

7. Create locale-specific .po files

For each language you want to support, create a .po file based on the template (if you ran polingo extract with --locales, these files were created for you automatically):

# Create directories
mkdir -p locales/en locales/es

# Copy template to create new locale files
cp locales/messages.pot locales/en/messages.po
cp locales/messages.pot locales/es/messages.po

Edit each .po file to add translations. For example, locales/es/messages.po:

msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

msgid "Welcome"
msgstr "Bienvenido"

msgid "Hello {name}!"
msgstr "¡Hola {name}!"

msgid "You have {n} message"
msgid_plural "You have {n} messages"
msgstr[0] "Tienes {n} mensaje"
msgstr[1] "Tienes {n} mensajes"

Pro tip: Use translation tools like Poedit, Lokalize, or web platforms like Weblate to edit .po files more easily.

8. Compile .po files to JSON for the web loader

Convert your .po files to JSON format for the browser:

# Compile all .po files in locales/ directory to public/i18n/
pnpm polingo compile locales -o public/i18n --format json

# This creates:
# public/i18n/en/messages.json
# public/i18n/es/messages.json

The --format json option generates JSON catalogs that @polingo/web can fetch. If you were using @polingo/node instead, you could use --format mo to generate binary .mo files.

9. Validate your translations (optional)

Before deploying, validate that all translations are complete:

# Basic validation
pnpm polingo validate locales

# Strict mode (fails on fuzzy or missing translations)
pnpm polingo validate locales --strict

10. Add to your build process

Add these commands to your package.json:

{
  "scripts": {
    "extract": "polingo extract src",
    "compile": "polingo compile locales -o public/i18n --format json",
    "validate": "polingo validate locales --strict",
    "prebuild": "pnpm extract && pnpm compile && pnpm validate",
    "build": "vite build"
  }
}

Now running pnpm build will automatically extract, compile, and validate translations before building your app.

Translation workflow

Here's the recommended workflow for managing translations:

  1. Development: Write code using t(), tn(), tp(), tnp(), and <Trans> components
  2. Extract: Run pnpm extract to update your locale catalogs (add --keep-template if you need the messages.pot artifact)
  3. Update .po files: Update each locale's .po file with new strings (manually or with tools)
  4. Translate: Add translations for new strings in each .po file
  5. Validate: Run pnpm validate --strict to check for missing translations
  6. Compile: Run pnpm compile to generate JSON catalogs for the web
  7. Test: Test your app with different locales
  8. Commit: Commit both .po files and compiled JSON files to version control

Updating translations

When you add new translatable strings to your code:

# Extract new strings (this updates messages.pot)
pnpm extract

# Merge new strings into existing .po files using msgmerge (gettext tool)
msgmerge --update locales/en/messages.po locales/messages.pot
msgmerge --update locales/es/messages.po locales/messages.pot

# Or simply copy and manually merge if you don't have gettext installed
cp locales/messages.pot locales/es/messages.po  # Then manually add translations

# Compile to JSON
pnpm compile

# Validate
pnpm validate --strict

API reference

PolingoProvider

Props:

  • translator?: Translator – Pre-initialized translator instance (useful for SSR)
  • create?: CreatePolingoOptions | (() => Promise<WebPolingoInstance>) – Configuration object or factory function to create translator
  • children: ReactNode – Your app components
  • loadingFallback?: ReactNode – Optional UI to display while loading
  • onError?: (error: unknown) => void – Error callback for loading or locale switching failures

The provider accepts either:

  1. A configuration object (recommended for most cases)
  2. A factory function that returns a Polingo instance (for advanced initialization)

The provider exposes loading and error states while catalogs are being fetched.

Using configuration object (recommended):

<PolingoProvider
  create={{
    locale: 'en',
    locales: ['en', 'es'],
    loader: { baseUrl: '/i18n' },
  }}
>
  <App />
</PolingoProvider>

Using factory function (advanced):

import { createPolingo } from '@polingo/web';

<PolingoProvider
  create={() =>
    createPolingo({
      locale: 'en',
      locales: ['en', 'es'],
      loader: { baseUrl: '/i18n' },
    })
  }
>
  <App />
</PolingoProvider>;

useTranslation()

Returns an object with:

  • t(msgid, vars?) – Basic translation with optional variable interpolation
  • tp(context, msgid, vars?) – Translation with context (disambiguates homonyms)
  • tn(msgid, msgidPlural, count, vars?) – Plural-aware translation
  • tnp(context, msgid, msgidPlural, count, vars?) – Plural translation with context
  • locale: string – Current locale code
  • setLocale(locale: string): Promise<void> – Switch to a different locale
  • loading: boolean – True while catalogs are loading
  • error: Error | null – Error if catalog loading failed
function MyComponent() {
  const { t, tn, tp, tnp, locale, setLocale, loading, error } = useTranslation();

  if (loading) return <div>Loading translations...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>{t('Hello')}</div>;
}

useTranslator() / usePolingo()

Low-level hook that returns the Translator instance directly:

const translator = useTranslator();
translator.t('Hello');
translator.setLocale('es');

useLocale()

Returns just the current locale string:

const locale = useLocale();

Trans

Props:

  • message: string – Translation key with <0>, <1>, etc. placeholders
  • components?: ReactNode[] – Array of React elements to interpolate
  • vars?: Record<string, string | number> – Variables for {varName} placeholders
  • context?: string – Optional context (uses tp instead of t)
  • count?: number – For plural forms (uses tn or tnp)
  • plural?: string – Plural form of message (required when count is provided)
<Trans
  message="I agree to the <0>terms</0>"
  components={[<a href="/terms" />]}
/>

<Trans
  message="You have <0>{n}</0> new message"
  plural="You have <0>{n}</0> new messages"
  count={5}
  components={[<strong />]}
  vars={{ n: 5 }}
/>

Advanced patterns

Loading states

Handle loading and error states gracefully:

function App() {
  return (
    <PolingoProvider
      create={{
        locale: 'en',
        locales: ['en', 'es'],
        loader: { baseUrl: '/i18n' },
      }}
      loadingFallback={<div>Loading translations...</div>}
      onError={(error) => console.error('Translation error:', error)}
    >
      <AppContent />
    </PolingoProvider>
  );
}

function AppContent() {
  const { loading, error } = useTranslation();

  if (loading) {
    return <div>Loading translations...</div>;
  }

  if (error) {
    return <div>Failed to load translations: {error.message}</div>;
  }

  return <YourApp />;
}

Server-side rendering (SSR)

For SSR frameworks like Next.js or Remix, create the translator on the server and pass it to the provider:

// Server-side (e.g., Next.js getServerSideProps)
import { createPolingo } from '@polingo/web';

export async function getServerSideProps({ locale }) {
  const translator = await createPolingo({
    locale,
    locales: ['en', 'es'],
    loader: { baseUrl: '/i18n' },
  });

  return {
    props: {
      translator: translator.serialize(), // If you implement serialization
    },
  };
}

// Client-side
function App({ translator }) {
  return (
    <PolingoProvider translator={translator}>
      <YourApp />
    </PolingoProvider>
  );
}

Custom loader paths

Customize where catalogs are loaded from:

<PolingoProvider
  create={{
    locale: 'en',
    locales: ['en', 'es'],
    loader: {
      buildUrl: (locale, domain) => `https://cdn.example.com/translations/${locale}/${domain}.json`,
      requestInit: {
        credentials: 'include',
        cache: 'force-cache',
      },
    },
  }}
>
  <App />
</PolingoProvider>

Multiple domains

Use different translation domains (e.g., messages, errors, admin):

<PolingoProvider
  create={{
    locale: 'en',
    locales: ['en', 'es'],
    domain: 'admin', // Default domain
    loader: { baseUrl: '/i18n' },
  }}
>
  <App />
</PolingoProvider>

// This will load /i18n/en/admin.json

Runtime locale detection

Detect locale from URL, localStorage, or browser settings:

const getUserLocale = (): string => {
  // 1. Check URL parameter
  const urlParams = new URLSearchParams(window.location.search);
  const urlLocale = urlParams.get('locale');
  if (urlLocale) return urlLocale;

  // 2. Check localStorage
  const savedLocale = localStorage.getItem('locale');
  if (savedLocale) return savedLocale;

  // 3. Check browser language
  const browserLocale = navigator.language.split('-')[0]; // 'en-US' -> 'en'
  if (['en', 'es', 'fr'].includes(browserLocale)) return browserLocale;

  // 4. Default fallback
  return 'en';
};

const App = () => (
  <PolingoProvider
    create={{
      locale: getUserLocale(),
      locales: ['en', 'es', 'fr'],
      loader: { baseUrl: '/i18n' },
    }}
  >
    <YourApp />
  </PolingoProvider>
);

Persisting locale choice

Save the user's locale preference:

function LanguageSwitcher() {
  const { locale, setLocale } = useTranslation();

  const handleLocaleChange = async (newLocale: string) => {
    await setLocale(newLocale);
    localStorage.setItem('locale', newLocale);
  };

  return (
    <select value={locale} onChange={(e) => handleLocaleChange(e.target.value)}>
      <option value="en">English</option>
      <option value="es">Español</option>
      <option value="fr">Français</option>
    </select>
  );
}

Related packages

  • @polingo/core – Translation runtime shared by all adapters
  • @polingo/node – Filesystem loader, middleware, and watcher for Node.js
  • @polingo/web – Fetch-based loader for browsers that pairs well with React
  • @polingo/cli – Command line tooling for extraction, compilation, and validation

License

MIT © Reinier Hernández Avila