ferrocat 0.2.0

Rust-first gettext and ICU toolkit for PO parsing, compilation, and runtime formatting.
Documentation
# ferrocat

`ferrocat` is the public Rust crate in the `ferrocat` workspace.

It provides:

- PO parsing and serialization
- ICU MessageFormat parsing and serializable compilation
- Catalog compilation for serializable host bindings
- Message ID generation and plural helpers
- A Rust-only runtime module for locale-aware formatting and tag rendering

The crate is intended to be usable directly from Rust and to serve as the
shared core for thin host bindings such as Node.js.

## Installation

```bash
cargo add ferrocat
```

## Parse and stringify PO files

```rust
use ferrocat::{parse_po, stringify_po, SerializeOptions};

let po = parse_po(
    r#"
msgid ""
msgstr ""
"Language: de\n"

msgid "Hello"
msgstr "Hallo"
"#,
);

assert_eq!(po.items[0].msgid, "Hello");
assert_eq!(po.items[0].msgstr, vec!["Hallo"]);

let rendered = stringify_po(&po, SerializeOptions::default());
assert!(rendered.contains(r#"msgid "Hello""#));
```

## Compile ICU messages to a serializable payload

```rust
use ferrocat::{compile_icu, CompileIcuOptions, SerializedCompiledMessageKind};

let compiled = compile_icu(
    "{count, plural, one {# file} other {# files}}",
    &CompileIcuOptions::new("en"),
)
.expect("message should compile");

match compiled.kind {
    SerializedCompiledMessageKind::Icu { ast } => assert!(!ast.is_empty()),
    other => panic!("expected icu payload, got {other:?}"),
}
```

## Compile catalogs to a serializable payload

```rust
use ferrocat::{
    compile_catalog, Catalog, CatalogEntry, CatalogTranslation, CompileCatalogOptions,
    SerializedCompiledMessageKind,
};

let catalog = Catalog::from([(
    "Hello {name}!".to_owned(),
    CatalogEntry {
        translation: Some(CatalogTranslation::Singular("Hallo {name}!".to_owned())),
        ..CatalogEntry::default()
    },
)]);

let compiled = compile_catalog(&catalog, &CompileCatalogOptions::new("de"))
    .expect("catalog should compile");
assert_eq!(compiled.entries.len(), 1);
match &compiled.entries[0].message.kind {
    SerializedCompiledMessageKind::Icu { ast } => assert!(!ast.is_empty()),
    other => panic!("expected icu payload, got {other:?}"),
}
```

## Rust runtime formatting

For direct runtime formatting in Rust, use [`ferrocat::runtime`]. It exposes the
runtime compiler, compiled message/catalog types, and host hooks such as
`FormatHost`.

That keeps the crate root host-neutral for thin bindings while preserving the
richer Rust-native execution model for direct integrations.