Parse, compile & transform PO files — 20× faster
pofile-ts is a modern i18n toolkit for GNU gettext PO files. Not just a parser — includes an ICU compiler with 4× faster runtime than Lingui/FormatJS, native CLDR plural rules, and format conversion helpers. Zero dependencies. TypeScript-first. Built for Node 20+, Bun, and modern browsers.
- 20× faster parsing — Hand-optimized with first-char dispatch and fast-paths. No regex soup.
- ICU Compiler — Compile ICU messages to JavaScript functions. 4× faster runtime than Lingui and FormatJS.
- Native CLDR plurals — Uses
Intl.PluralRulesfor all 100+ locales. Zero CLDR data in bundle. - CSP-safe — No
eval(), nonew Function(). Works in strict security environments. - Modern-first — Built for Node 20+, ESM-native, tree-shakeable. No legacy polyfills.
- Zero dependencies — ~11KB full, ~5KB tree-shaken. No transitive deps, no supply chain bloat.
- 📖 Parse PO files from strings — 20× faster than pofile, 7× faster than gettext-parser
- ✏️ Serialize PO files back to strings — 2.5× faster than pofile, 5× faster than gettext-parser
- 🎯 Full PO support — headers, comments, flags, plurals, message context
- 🌍 CLDR plural rules — Uses native
Intl.PluralRules, zero bundle size for CLDR data - 🔄 ICU MessageFormat — Convert between Gettext plurals and ICU syntax
- 🧩 ICU Parser — Parse and analyze ICU messages (<3KB gzipped, 5× faster than FormatJS)
- ⚡ ICU Compiler — Compile ICU messages to fast JavaScript functions (4× faster at runtime)
- 🔢 Plural helpers — Get categories, counts, and selector functions for any locale
- 🆕 Extended Intl Formatters — Lists, durations, relative times, and display names built-in
- 🎨 50+ built-in styles —
compact,percent,bytes,iso,relative, and more - 🔧 Custom styles — Register your own
Intl.NumberFormat,DateTimeFormat,ListFormatoptions - 💱 Dynamic currency — Currency code read from message values at runtime
- 🏭 Factory pattern —
createIcuCompiler()for pre-configured, reusable compilers
- 📦 Zero dependencies — No bloat, no supply chain risk
- 🌳 Tree-shakeable — ~5KB for PO parsing only, ~11KB full
- 💎 TypeScript-first — Written in TypeScript, full type definitions
- 🛡️ CSP-safe — No
eval(), nonew Function() - ⚡ Modern-first — Node 20+, ESM-native, no legacy polyfills
- Vite/Webpack plugins — Parse and compile PO files at build time for zero runtime cost
- TMS pipelines — Crowdin, Lokalise, Phrase — sync and transform translations
- CI/CD validation — Validate plural forms, variables, and syntax in PRs
- Custom tooling — Low-level APIs for message extraction and code generation
npm install pofile-tsimport { parsePo, stringifyPo } from "pofile-ts"
const po = parsePo(`
msgid "Hello"
msgstr "Hallo"
`)
console.log(po.items[0].msgid) // "Hello"
console.log(po.items[0].msgstr) // ["Hallo"]
console.log(stringifyPo(po))A fast, lightweight ICU MessageFormat parser — 5× faster and 4× smaller than FormatJS:
import { parseIcu, extractVariables, validateIcu, hasPlural } from "pofile-ts"
// Parse ICU messages into an AST
const result = parseIcu("{count, plural, one {# item} other {# items}}")
if (result.success) {
console.log(result.ast) // AST with plural node
}
// Extract variables from messages
extractVariables("{name} has {count} messages")
// → ["name", "count"]
// Validate syntax before use
const validation = validateIcu("{gender, select, male {He} other {They}}")
console.log(validation.valid) // true
// Check message structure
hasPlural("{n, plural, one {#} other {#}}") // true
hasPlural("Hello {name}") // falseSupports ICU MessageFormat v1: arguments, plurals, selects, selectordinals, number/date/time formatting, tags, and escaping. Trade-offs for size: no AST location tracking, styles stored as opaque strings.
Compile ICU messages to fast JavaScript functions — 4× faster than Lingui and FormatJS at runtime:
import { compileIcu, compileCatalog, generateCompiledCode } from "pofile-ts"
// Compile a single message
const greet = compileIcu("Hello {name}!", { locale: "en" })
greet({ name: "World" }) // → "Hello World!"
// Full ICU support: plurals, select, number/date/time, tags
const msg = compileIcu("{count, plural, one {# item} other {# items}} in <link>cart</link>", {
locale: "en"
})
msg({ count: 5, link: (text) => `<a>${text}</a>` })
// → "5 items in <a>cart</a>"
// Dynamic currency from values
const price = compileIcu("{amount, number, currency}", { locale: "de" })
price({ amount: 99.99, currency: "EUR" }) // → "99,99 €"
price({ amount: 99.99, currency: "USD" }) // → "99,99 $"
// Custom format styles with full Intl options
const size = compileIcu("{bytes, number, filesize}", {
locale: "en",
numberStyles: {
filesize: { style: "unit", unit: "kilobyte", unitDisplay: "short" }
}
})
size({ bytes: 512 }) // → "512 kB"
// Compile an entire catalog at runtime
const compiled = compileCatalog(catalog, { locale: "de" })
compiled.format("messageId", { name: "Sebastian" })
// Or generate static code for build-time compilation
const code = generateCompiledCode(catalog, { locale: "de" })
// → TypeScript file with pre-compiled functionsSupports named tags (<link>), numeric tags (<0>, <1> — Lingui-style), and React components (returns array when tag functions return objects).
Go beyond standard ICU with built-in support for modern Intl APIs:
import { compileIcu } from "pofile-ts"
// Lists — "Alice, Bob, and Charlie" or "Alice, Bob, or Charlie"
const list = compileIcu("{authors, list}", { locale: "en" })
list({ authors: ["Alice", "Bob", "Charlie"] }) // → "Alice, Bob, and Charlie"
// Relative time — "in 3 days" or "2 hours ago"
const ago = compileIcu("{days, ago, day}", { locale: "de" })
ago({ days: -2 }) // → "vor 2 Tagen"
// Display names — Localized country, language, currency names
const name = compileIcu("{lang, name, language}", { locale: "de" })
name({ lang: "en" }) // → "Englisch"
// Durations — "2 hours, 30 minutes" (Baseline 2025)
const dur = compileIcu("{time, duration, short}", { locale: "en" })
dur({ time: { hours: 2, minutes: 30 } }) // → "2 hr, 30 min"All formatters use native browser APIs — zero additional bundle size. See browser support.
For full documentation including API reference, i18n helpers, and migration guide:
Speed matters for build tools and CI pipelines. pofile-ts is hand-optimized for performance — no regex soup, no unnecessary allocations, just fast parsing.
Benchmarked on Apple M1 Ultra, Node.js 22. Relative performance is consistent across different hardware.
10,000 entries (~10% plurals):
| Library | Parsing | Serialization |
|---|---|---|
| pofile-ts | 209 ops/s | 256 ops/s |
| gettext-parser | 28 ops/s | 54 ops/s |
| pofile | 8 ops/s | 100 ops/s |
→ 20× faster parsing vs pofile, 7× faster vs gettext-parser
Realistic messages with plurals, selects, nested structures, and tags:
| Library | Speed | Bundle (gzip) |
|---|---|---|
| pofile-ts | 5× faster | <3KB |
| @formatjs/icu-messageformat-parser | baseline | ~9KB |
→ 5× faster, 4× smaller bundle
Compiling ICU messages to functions and executing them:
| Metric | pofile-ts | vs intl-messageformat | vs @lingui (compiled) |
|---|---|---|---|
| Compilation | 409k op/s | 7× faster | — |
| Runtime | 792k op/s | 3× faster | 4× faster |
| Catalog (200) | ~1.35M/s | 7× faster | — |
→ 4× faster at runtime vs Lingui and FormatJS
The full library is ~11KB gzipped. Tree-shaking lets you import only what you need:
| Export | Gzipped |
|---|---|
| PO parsing | ~5KB |
| Plural helpers | ~1KB |
| ICU conversion | ~2KB |
| ICU parser | ~3KB |
Plural helpers use native Intl.PluralRules — no CLDR data in the bundle.
All exports are named exports — modern bundlers (Vite, esbuild, Rollup, webpack) automatically tree-shake unused code.
packages/pofile-ts/ # The library (published to npm)
apps/docs/ # Documentation site (Fumadocs)
benchmark/ # Performance benchmarks
pnpm install
pnpm test # Run tests
pnpm build # Build library
pnpm docs:dev # Start docs dev serverOriginally forked from pofile by Ruben Vermeersch. Completely rewritten with modern TypeScript, expanded with CLDR plural support, ICU conversion, and comprehensive i18n helpers.
Maintained by Sebastian Software.
MIT — Use it freely in personal and commercial projects.