Part of Ferramenta, a family of Rust tools.
Rust-native translation catalog engine for Gettext PO, ICU MessageFormat, and FCL catalogs.
Documentation: ferrocat.dev — guides, API overview, benchmarks, and architecture decisions.
ferrocat is a Rust-native translation catalog engine. It treats localized copy as product data: parse it, update it, review it, measure it, audit it, and compile it into runtime payloads your application can ship with confidence.
The practical problem is simple: translations change constantly, and most projects need more than "load a JSON file at runtime." Teams need source identity, translator context, reviewable diffs, coverage numbers, release checks, fallback behavior, and a runtime shape that does not hide catalog problems until production. Ferrocat is built for that middle layer.
You do not need to know PO files, ICU MessageFormat, or FCL to get started. What matters is that the catalog stays inspectable, testable, benchmarked, and portable across host-language adapters, instead of disappearing into format-specific tooling.
pofile-ts is Ferrocat's TypeScript predecessor. It is retained as an archived reference and benchmark baseline; further development is focused on Ferrocat.
cargo add ferrocatThe public Rust entry point is the ferrocat crate. It re-exports the stable Rust surface from the lower-level workspace crates:
ferrocat: umbrella crate and recommended dependency for application codeferrocat-po: PO parsing, serialization, low-level merge helpers, and feature-gated higher-level catalog workflowsferrocat-icu: ICU MessageFormat parsing, structural helpers, source/translation compatibility diagnostics, and semantic message metadata helpers
For non-Rust CI release gates, install the CLI package and run ferrocat audit:
cargo install ferrocat-cli
ferrocat audit --source-locale en --source locales/en.po --target de=locales/de.poThe ferrocat-cli GitHub release also publishes a prebuilt Linux musl archive for x86_64-unknown-linux-musl. The archive is named ferrocat-<version>-x86_64-unknown-linux-musl.tar.gz and contains the ferrocat binary plus license and README metadata.
use ferrocat::{SerializeOptions, parse_po, stringify_po};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut file = parse_po(
r#"
msgid "hello"
msgstr "world"
"#,
)?;
file.items[0].msgstr = "Welt".to_owned().into();
let rendered = stringify_po(&file, &SerializeOptions::default());
assert!(rendered.contains(r#"msgstr "Welt""#));
Ok(())
}For the common "merge fresh extracted messages into an existing catalog" workflow, merge_catalog is the lean Gettext-style entry point. For N-way catalog overlays and msgcat-style set operations, use combine_catalogs; when catalogs already live on disk, combine_catalog_files adds format inference and atomic output replacement around the same semantics. For version-control workflows, merge_catalogs_three_way accepts explicit ancestor, ours, and theirs roles and preserves deletions instead of treating the ancestor as another overlay. Use convert_catalog or convert_catalog_file for explicit ICU-native PO ↔ FCL conversion without treating conversion as a one-input combine. For release checks across a source catalog and target catalogs, use audit_catalogs. For dashboards or translator handoffs, use measure_catalog_coverage and review_catalogs. For application delivery, compile requested-locale artifacts with fallback and ICU diagnostics; use compile_catalog_artifact_report when host tooling also needs per-message resolution provenance without changing the runtime artifact shape.
Coverage, audit, and review inputs should be parsed with
parse_catalog_for_review. It returns a normalized catalog that retains only
semantic fuzzy identities, so active #, fuzzy PO and f=fuzzy FCL entries do
not count as translated. The ordinary parse_catalog path continues to skip
opaque flags and translator comments for high-throughput consumers that do not
request report state.
Beyond the basics, Ferrocat exposes byte-oriented and allocation-light borrowed parsers for hot paths, ICU analysis and source/translation compatibility checks (analyze_icu, compare_icu_messages, validate_icu_formatter_support), policy-aware apostrophe canonicalization and compiled-key derivation, ICU-aware pseudolocalization, semantic metadata normalization around msgid + msgctxt, and AI-translation metadata that high-level writers clear automatically once a human edits the text. ICU scope is MessageFormat v1; MessageFormat 2 is tracked as a future standard but is not a near-term target.
- One reliable catalog core. Keep source text, contexts, translations, notes, source origins such as
src/App.tsx#CheckoutButtonorsrc/i18n.ts#formatInvoiceStatus, plural forms, and obsolete entries in a model that application code can reason about. - Predictable updates. Merge newly extracted messages into existing catalogs without fuzzy guessing, hidden identity changes, or silent conflict resolution.
- Deletion-aware collaboration. Merge explicit ancestor, ours, and theirs catalogs while preserving deletions and reporting modify/delete conflicts through structured errors or diagnostics.
- Explicit format conversion. Convert ICU-native PO and FCL content or files with separate source and target modes, message-level metadata preservation, and atomic file replacement.
- Lingui-compatible catalog order. Sort message and context identities with the CLDR root order used by
Intl.Collator("en-US")across PO, FCL, update, and combine output. PO can group by source origin while retaining the same identity tie-break; FCL always keeps its own collated line-order invariant. - Release-ready QA. Audit catalog sets for missing locales, missing translations, empty translations, stale target messages, ICU mistakes, metadata conflicts, and obsolete entries.
- Coverage and review reports. Turn catalog state into completion counters, translator handoff diffs, and machine-managed value freshness checks instead of rebuilding those rules in every host tool.
- Safer rich messages. Analyze placeholders, formatters, plural/select branches, and rich-text tags so a translation cannot accidentally drop a required runtime value.
- AI-native metadata. Tag any machine-written value with a top-level integrity lock plus optional model and confidence. Ship it as-is by default; when a human corrects one, the lock stops matching, so the next re-translation run will not silently overwrite their fix.
- Runtime artifacts you can explain. Compile catalogs into host-neutral payloads with stable keys, fallback behavior, missing reports, optional ICU diagnostics, and provenance rows that show where each runtime string came from.
- Pseudo-locale QA. Generate ICU-aware pseudolocalized messages and runtime artifacts without damaging placeholders, plural selectors, rich-text tags, or formatter syntax.
- Storage that survives a merge. Use PO when translator tooling reads the file directly, or FCL when several people and jobs touch the same locale: one canonical entry per line keeps ordinary git merges from losing untouched translations.
- Room for host frameworks. Palamedes can own JS/TS extraction, bindings, and framework integration while Ferrocat owns the catalog behavior that should stay consistent underneath.
- Measured behavior. PR-visible regression scenarios cover parsing, serialization, merge, combine, audit, coverage/review, and runtime artifact compilation, backed by fixtures, upstream-derived conformance cases, and coverage gates.
Ferrocat is part of the Palamedes ecosystem. Palamedes is the OSS i18n framework for JavaScript and TypeScript apps; Ferrocat supplies the shared catalog engine underneath it. JavaScript and TypeScript access is a Palamedes concern, including the palamedes-node N-API bridge in that repository; this repository stays the pure-Rust catalog engine.
Ferrocat is a new catalog layer, but it is not invented in a vacuum. It keeps the useful parts of established translation workflows and makes them available through a Rust API:
- PO catalogs for translator-friendly source, translation, context, comment, origin, plural, and obsolete-entry handling.
- ICU MessageFormat v1 for richer messages with arguments, formatting, plurals, selects, and rich-text tags.
- FCL catalogs (Ferrocat Catalog Lines) for Git review, automation, and pipelines: one entry per line, deterministically sorted, and designed to preserve unrelated edits during ordinary 3-way merges.
- Machine-managed value metadata for AI-assisted workflows: a top-level integrity lock that detects later by-hand edits to any machine-set value, plus optional AI provenance.
- Structured diagnostics instead of ad hoc text output, so CI, editors, and host frameworks can consume the same report.
Ferrocat is not trying to replace every part of an i18n stack. If you already know the existing landscape, this is the gap it fills:
| Common approach | What works well | Where Ferrocat helps |
|---|---|---|
| GNU gettext-style tooling | Mature PO conventions, translator metadata, broad ecosystem familiarity | Rust-native APIs, explicit conflict policy, structured diagnostics, ICU-native workflows, and app-ready runtime artifacts |
| Framework-specific i18n packages | Great authoring ergonomics and runtime adapters inside one host ecosystem | Shared catalog semantics that can be reused by Palamedes or other adapters instead of reimplemented per framework |
| Custom JSON catalogs | Easy loading and deployment | Stronger update semantics, reviewable FCL storage, source/translation QA, and a path back to translator-friendly PO workflows |
| ICU-only message handling | Powerful plural, select, formatter, and rich-text syntax | Structural analysis and compatibility checks that catch missing arguments, formatter drift, tag mismatch, and branch changes before shipping |
Ferrocat does not currently ship first-party WebAssembly, N-API, Python, Go, or other host-language bindings from this repository. Host integration is layered on top of the Rust API: JS/TS applications should look to Palamedes and its palamedes-node bridge, while other ecosystems can use the Rust crates directly, the ferrocat CLI audit gate, or host-specific bindings maintained at that layer.
Ferrocat's benchmark suite compares file-to-file catalog workloads against common PO libraries and tools on the same fixtures. The published table below uses the 10k-message gettext catalog from the checked-in benchmark reports (Apple M1 Ultra, median throughput):
| Workload | Ferrocat | pofile-ts (Node) | gettext/gettext (PHP) | Babel (Python) | polib (Python) | GNU msgmerge |
|---|---|---|---|---|---|---|
| Parse | 725 MiB/s | 149 | 53 | - | 22 | - |
| Update with new strings | 291 MiB/s | 43 | 16 | - | 8.1 | 4.8 |
| Full catalog update | 150 MiB/s | 42 | - | 8.9 | 8.2 | 4.8 |
The Node baseline is pofile-ts, a speed-focused fork of the widely used pofile, so the JavaScript comparison targets a fast implementation rather than an easy one. The update benchmark parses existing and freshly extracted files, merges by exact identity, and serializes the result, which is where Ferrocat's byte-oriented scanning and move-not-clone catalog paths matter most.
The full catalog update row is Ferrocat's high-level update_catalog on the same files: on top of the plain update it analyzes ICU message structure, tracks placeholders, and produces deterministic output. The comparison now includes Babel's real Catalog.update path for Python; even with that extra work, Ferrocat's full update runs about 3.4x ahead of the fastest non-Ferrocat workflow update and roughly 31x ahead of GNU msgmerge.
See the benchmark methodology and the checked-in reports under benchmark/results/ for host details, fixture definitions, noise handling, and the full matrix.
At the high-level catalog layer, ferrocat supports three explicit combinations of storage format and message semantics. You do not need to choose every mode on day one; the point is that migrations stay visible in code.
| Mode | Storage format | Message model | Use when you want to... |
|---|---|---|---|
| Classic Gettext catalog mode | Gettext PO | Gettext-compatible plurals | stay close to traditional gettext catalogs and msgid_plural / msgstr[n] workflows |
| ICU-native Gettext PO mode | Gettext PO | ICU MessageFormat | keep Gettext PO files and tooling, but author richer ICU plural/select/formatting messages |
| ICU-native FCL catalog mode | FCL catalog storage | ICU MessageFormat | move to one-entry-per-line, tab-separated records that are easier to diff, merge, batch, and hand to external systems |
There is intentionally no FCL + gettext-plural mode; gettext plural behavior stays a PO concern, while FCL is the ICU-native machine storage format for ICU-native catalogs. Generate FCL through the catalog layer by choosing CatalogMode::IcuFcl in parse_catalog, update_catalog, conversion, or file-based update flows; keep PO when external translator tools need gettext compatibility. The in-repo FCL format specification documents the exact line format, escaping rules, and architecture decisions behind that storage mode.
The published crates default to the full current API surface. Use
default-features = false when you want the low-level PO and ICU parsers without
the catalog workflow layer.
catalogenables high-level catalog parsing, updates, combining, conversion, audits, machine-translation metadata, plural handling, FCL storage, and runtime artifact compilation.serdeenables serialization support for tooling, cache, schema, report, and runtime artifact shapes.compile,mt, andpluralsare reserved subsystem aliases that currently implycatalog; they do not reduce or split the catalog API surface yet.
See the API overview for the crate-by-crate feature profile details.
- MSRV: Rust
1.94.0 - MSRV policy: align with OXC when practical, while avoiding churn from tracking only the newest stable toolchain
- MSRV bumps: raising the MSRV is treated as a minor-version change and called out in the changelog; patch releases do not raise the MSRV
- Prebuilt CLI target:
x86_64-unknown-linux-muslis validated in CI and published as a smoke-tested GitHub Release archive forferrocat-cli - Semver: the public API follows semantic versioning. Until the API surface settles and the crate has more downstream consumers, minor releases may still ship reviewed breaking cleanups (renames, stricter types,
#[non_exhaustive]additions); every break is listed in the changelog with migration notes - Error surface: PO parse errors stay intentionally compact but expose
message()plus optionalposition()metadata with zero-based byte offset and one-based line/column when source context is available - Documentation surface: README examples, rustdoc examples, and the docs site aim to stay aligned
If you already know what kind of question you have, these are the fastest entry points:
- Getting started for installation, quick start, and the main next steps
- Catalog modes for choosing PO or FCL storage with gettext or ICU message semantics
- FCL format specification for the repository-local line format and escaping reference
- Ferrocat and Palamedes for the relationship between the catalog engine and the JS/TS i18n framework
- Releases and upgrading for changelogs, lockstep versions, and safe upgrades
- API overview for choosing between PO core, catalog workflows, and ICU helpers
- Gettext task landscape for the workflow-level map across GNU gettext, common libraries, and Ferrocat
- Performance docs for benchmark methodology, fixtures, and history
- Quality docs for conformance and coverage
- ADR index for architecture decisions and longer-term technical direction
- Documentation site
- docs.rs crate docs
ferrocatchangelog- GitHub Releases
- GitHub repository
- Palamedes i18n framework
- Contributing guide
- Security policy
- Code of Conduct
- Support guide
Ferramenta — A family of Rust tools.
Engines
| Tool | Job |
|---|---|
| ferroni | Oniguruma-compatible regex engine |
| ferriki | Shiki-compatible syntax highlighting |
| ferromark | Markdown to HTML, sanitized by default |
| ferrolex | Spell checking for text and code |
| ferralk | Glob matching and parallel filesystem walking |
| ferrugo | PDF previews for untrusted files |
| ferrolink | Links, anchors and assets checked against the finished build |
| ferrigura | Mermaid diagrams to SVG without a browser |
Applications
| Tool | Job |
|---|---|
| palamedes | Internationalization for TypeScript applications |
| dalo | Your team's agent setup, versioned like code |
| ardo | Documentation sites built with React |
TypeScript, React & Rust consulting
Experts in Agentic Software Development
Work with us · More open source
Copyright © 2026 Sebastian Software GmbH