@polingo/react
v0.0.3
Published
React bindings for the Polingo translation engine
Maintainers
Readme
@polingo/react
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
- Installation
- Quick start
- Step-by-step setup guide
- Translation workflow
- API reference
- Advanced patterns
- Related packages
- 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/reactFor development, you'll also want the CLI tooling:
npm install -D @polingo/cli
# or
pnpm add -D @polingo/cliQuick 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/cli3. 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 enThis 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.poEdit 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.jsonThe --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 --strict10. 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:
- Development: Write code using
t(),tn(),tp(),tnp(), and<Trans>components - Extract: Run
pnpm extractto update your locale catalogs (add--keep-templateif you need themessages.potartifact) - Update .po files: Update each locale's
.pofile with new strings (manually or with tools) - Translate: Add translations for new strings in each
.pofile - Validate: Run
pnpm validate --strictto check for missing translations - Compile: Run
pnpm compileto generate JSON catalogs for the web - Test: Test your app with different locales
- Commit: Commit both
.pofiles 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 --strictAPI reference
PolingoProvider
Props:
translator?: Translator– Pre-initialized translator instance (useful for SSR)create?: CreatePolingoOptions | (() => Promise<WebPolingoInstance>)– Configuration object or factory function to create translatorchildren: ReactNode– Your app componentsloadingFallback?: ReactNode– Optional UI to display while loadingonError?: (error: unknown) => void– Error callback for loading or locale switching failures
The provider accepts either:
- A configuration object (recommended for most cases)
- 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 interpolationtp(context, msgid, vars?)– Translation with context (disambiguates homonyms)tn(msgid, msgidPlural, count, vars?)– Plural-aware translationtnp(context, msgid, msgidPlural, count, vars?)– Plural translation with contextlocale: string– Current locale codesetLocale(locale: string): Promise<void>– Switch to a different localeloading: boolean– True while catalogs are loadingerror: 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. placeholderscomponents?: ReactNode[]– Array of React elements to interpolatevars?: Record<string, string | number>– Variables for{varName}placeholderscontext?: string– Optional context (usestpinstead oft)count?: number– For plural forms (usestnortnp)plural?: string– Plural form of message (required whencountis 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.jsonRuntime 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
