Modern internationalization (i18n) library using industry-standard .po and .mo files for JavaScript/TypeScript projects.
Caution
Alpha Notice: Polingo is currently in an alpha state. Breaking changes are becoming less common, but anything and everything may still change.
- Standard Gettext Support: Use
.poand.mofiles, the industry standard for translations - Automatic Catalog Detection: Seamlessly load either
.poor.mocatalogs from disk - Environment-Agnostic Core: Universal translation engine that works anywhere
- Node.js Integration: Filesystem loader with optional hot-reload via chokidar
- Browser Ready: Fetch loader backed by localStorage caching
- Pluralization: Full support for plural forms across different languages
- Context Support: Disambiguate identical strings with different meanings
- Variable Interpolation: Dynamic content with placeholder replacement
- Middleware Ready: Express and Fastify helpers with locale detection hooks
- TypeScript First: Strict types, excellent IntelliSense
- Flexible Caching: Choose in-memory, TTL, or no caching depending on your needs
Polingo is organized as a monorepo with the following packages:
- @polingo/core: Environment-agnostic translation engine
- @polingo/node: Node.js loader with filesystem support and middleware
- @polingo/web: Browser adapter using fetch + localStorage caching
- @polingo/react: React bindings with hooks, context provider, and Trans component
- @polingo/cli: Command-line tools for extraction, compilation, and validation
- create-polingo-app: Interactive scaffolding for example applications
Per-package Codecov coverage (updated on every successful make coverage run in CI):
| Package | Coverage |
|---|---|
| @polingo/core | |
| @polingo/node | |
| @polingo/react | |
| @polingo/vue | |
| @polingo/web |
Choose the package that fits your environment:
For Node.js applications:
npm install @polingo/node
# or
pnpm add @polingo/node
# or
yarn add @polingo/nodeFor browser/React applications:
npm install @polingo/core @polingo/web @polingo/react
# or
pnpm add @polingo/core @polingo/web @polingo/reactFor CLI tooling (development dependency):
npm install -D @polingo/cli
# or
pnpm add -D @polingo/cliFor environment-agnostic usage:
npm install @polingo/coreScaffold a working starter directly from the examples/ catalog using the interactive creator:
pnpm create polingo-appYou'll be prompted to pick one of the maintained examples (React + Vite, Express, etc.) and a destination folder. The generator copies the selected template, refreshes the package name, and leaves you ready to install dependencies and start coding.
import { createPolingo } from '@polingo/node';
const polingo = await createPolingo({
locale: 'es',
locales: ['es', 'en', 'fr'],
directory: './locales',
fallback: 'en',
});
console.log(polingo.t('Hello')); // "Hola"
console.log(polingo.t('Hello, {name}!', { name: 'Juan' })); // "¡Hola, Juan!"
// Pluralization
console.log(polingo.tn('{n} item', '{n} items', 1, { n: 1 })); // "1 artículo"
console.log(polingo.tn('{n} item', '{n} items', 5, { n: 5 })); // "5 artículos"import { createPolingo } from '@polingo/web';
const polingo = await createPolingo({
locale: 'es',
locales: ['es', 'en'],
// Catalogs are served from /i18n by default; point baseUrl elsewhere if needed.
loader: { baseUrl: '/i18n' },
});
document.querySelector('#greeting')!.textContent = polingo.t('Hello');import express from 'express';
import { polingoMiddleware } from '@polingo/node';
const app = express();
app.use(
polingoMiddleware({
directory: './locales',
locales: ['es', 'en', 'fr'],
fallback: 'en',
})
);
app.get('/', (req, res) => {
const greeting = req.polingo.t('Welcome, {name}!', { name: 'User' });
res.send(greeting);
});
app.listen(3000);import fastify from 'fastify';
import { polingoMiddleware } from '@polingo/node';
const app = fastify();
app.addHook(
'onRequest',
polingoMiddleware({
directory: './locales',
locales: ['es', 'en', 'fr'],
fallback: 'en',
})
);
app.get('/', async (request, reply) => {
return request.polingo.t('Welcome');
});
app.listen({ port: 3000 });import { PolingoProvider, useTranslation, Trans } from '@polingo/react';
import { createPolingo } from '@polingo/web';
function App() {
return (
<PolingoProvider
create={() =>
createPolingo({
locale: 'en',
locales: ['en', 'es', 'fr'],
loader: { baseUrl: '/i18n' },
})
}
>
<MyComponent />
</PolingoProvider>
);
}
function MyComponent() {
const { t, tn, setLocale } = useTranslation();
return (
<div>
<h1>{t('Welcome to Polingo!')}</h1>
<p>{tn('You have {n} message', 'You have {n} messages', 3, { n: 3 })}</p>
<Trans
message="Read the <0>documentation</0> to learn more"
components={[<a href="/docs" />]}
/>
<button onClick={() => setLocale('es')}>Español</button>
</div>
);
}For a complete React example, see the React + Vite example.
Use the CLI to extract, compile, and validate translations:
# Install CLI tools
pnpm add -D @polingo/cli
# Extract translatable strings from source code (updates ./locales)
# Fuzzy matching is enabled by default to automatically detect similar strings
pnpm polingo extract
# Extract with fuzzy matching disabled
pnpm polingo extract --no-fuzzy
# Extract with custom fuzzy threshold (0-1, default: 0.6)
pnpm polingo extract --fuzzy-threshold 0.8
# After translating .po files, compile to runtime format
pnpm polingo compile locales -o public/i18n --format json
# Validate translations before deployment
pnpm polingo validate locales --strictFuzzy Matching: When enabled (default), the extract command automatically detects similar strings between catalog updates and marks them with the #, fuzzy flag, similar to msgmerge behavior. This helps translators identify strings that may need review when source text changes slightly. Obsolete entries (no longer in source) are marked with #~ for reference.
See the @polingo/cli documentation for detailed command reference.
Enable file watching so catalogs reload automatically when your .po/.mo files change:
const polingo = await createPolingo({
locale: 'es',
locales: ['es', 'en'],
directory: './locales',
watch: process.env.NODE_ENV === 'development',
debug: true,
});
// Later, when shutting down:
await polingo.stopWatching?.();Your translation files should be organized by locale:
locales/
├── es/
│ └── messages.po
├── en/
│ └── messages.po
└── fr/
└── messages.po
t(msgid, vars?)- Translate a messagetp(context, msgid, vars?)- Translate with contexttn(msgid, msgidPlural, count, vars?)- Translate with pluralizationtnp(context, msgid, msgidPlural, count, vars?)- Translate with context and pluralization
interface CreatePolingoOptions {
locale: string; // Initial locale (e.g. 'en')
locales: string[]; // Locales to preload during startup
directory: string; // Path to the locales folder
fallback?: string; // Fallback locale when a key is missing (default: 'en')
domain?: string; // Translation domain filename prefix (default: 'messages')
cache?: boolean; // Use in-memory caching (default: true)
watch?: boolean; // Watch .po/.mo files and reload on change (default: false)
debug?: boolean; // Log loading and cache activity (default: false)
}NodeLoader automatically looks for both <locale>/<domain>.po and <locale>/<domain>.mo, preferring .po when both exist.
The middleware shares the same options (minus locale) plus:
localeExtractor(req)– customize how the locale is detected (defaults toAccept-Languageor?locale=query parameter).perLocale– set totrueto create dedicated translator instances per locale instead of reusing one shared translator.
This project uses pnpm workspaces and includes a Makefile for common tasks. Recommended workflow:
# Install dependencies without touching the lockfile
pnpm install --frozen-lockfile
# Type safety and linting
pnpm typecheck
pnpm lint
pnpm format:check
# Unit tests
pnpm testOr use the Makefile shortcuts:
# Install dependencies
make install
# Build all packages
make build
# Run tests
make test
# Run tests with coverage
make coverage
# Run linter
make lint
# Clean build artifacts
make clean- Node.js >= 18.0.0
- pnpm >= 8.0.0 (developed with pnpm 10.x)
Check out our working examples to see Polingo in action:
- React + Vite Example: Full-featured React app demonstrating hooks, Trans component, locale switching, and complete translation workflow
For detailed documentation, see the individual package READMEs:
- @polingo/core documentation
- @polingo/node documentation
- @polingo/web documentation
- @polingo/react documentation
- @polingo/cli documentation
Contributions are welcome! Please feel free to submit a Pull Request.
If you discover a security vulnerability, please follow our security policy for responsible disclosure guidelines.
MIT © Reinier Hernández Avila