Expand description
§es-fluent-cli
The official command-line tool for es-fluent.
This tool automatically manages your Fluent (.ftl) translation files by analyzing your Rust code. It finds types with #[derive(EsFluent)] and creates corresponding message entries, so you don’t have to keep them in sync manually.
§Installation
cargo install es-fluent-cli --locked§Commands
Ensure you have an i18n.toml in your crate root:
# Default fallback language (required)
fallback_language = "en-US"
# Path to FTL assets relative to the config file (required)
assets_dir = "assets/locales"
# Features to enable if the crate’s es-fluent derives are gated behind a feature (optional)
fluent_feature = ["my-feature"]
# Optional allowlist of namespace values for FTL file splitting
namespaces = ["ui", "errors", "messages"]§Generate
When you add new localizable structs or enums to your code, run:
cargo es-fluent generateThis will:
- Scan your
src/directory. - Update
i18n/en-US/{your_crate}.ftl(andi18n/en-US/{your_crate}/{namespace}.ftlfor namespaced types).- New items: Added as new messages.
- Changed items: Variables updated (e.g. if you added a field).
- Existing translations: Preserved untouched.
Use --dry-run to preview changes without writing them. Use --force-run to bypass the staleness cache and force a rebuild.
If you configure namespaces = [...] in i18n.toml, string-based namespaces are validated against the allowlist by both the compiler (at compile-time) and the CLI (during generate and watch).
§Namespaces (optional)
You can split output into multiple files by annotating types:
#[derive(EsFluent)]
#[fluent(namespace = "ui")] // -> assets_dir/{locale}/{crate}/ui.ftl
struct Button;
#[derive(EsFluent)]
#[fluent(namespace = file)] // -> assets_dir/{locale}/{crate}/{file_stem}.ftl
struct Dialog;
#[derive(EsFluent)]
#[fluent(namespace(file(relative)))] // -> assets_dir/{locale}/{crate}/ui/button.ftl
struct Modal;§Watch
Same as generate, but runs in watch mode, updating FTL files as you type:
cargo es-fluent watch§Check
To ensure all your translations are valid and no keys are missing:
cargo es-fluent checkUse --all to check all locales, not just the fallback language, --ignore <CRATE> to skip specific crates, --force-run to bypass the staleness cache.
§Clean
Remove orphan keys and groups that are no longer present in your source code:
cargo es-fluent cleanUse --dry-run to preview changes without writing them. Use --all to clean all locales. Use --force-run to bypass the staleness cache.
§Clean Orphaned Files
Remove FTL files that are no longer tied to any registered types (e.g., when all items are now namespaced or the crate was deleted):
cargo es-fluent clean --orphaned --allThis compares files in non-fallback locales against the fallback locale (en-US by default). Files that exist in non-fallback locales but have no corresponding file in the fallback locale are considered orphaned and will be removed. The fallback locale itself is never modified.
Use --dry-run to preview which files would be removed without actually deleting them.
§Format
Standardize the formatting of your FTL files using fluent-syntax rules:
cargo es-fluent formatUse --dry-run to preview changes without writing them. Use --all to format all locales.
§Sync
Propagate keys from your fallback language to other languages (e.g., from en-US to fr and de), creating placeholders for missing translations:
cargo es-fluent syncUse --locale <LANG> to sync a specific locale, or --all to sync all locales, --dry-run to preview changes without writing them.
The sync command properly handles namespaced FTL files, creating matching subdirectories in target locales when syncing from the fallback locale.
§Limitations
The CLI runner links workspace crates as library targets only. If you define
#[derive(EsFluent*)] types exclusively in a binary target, they won’t be registered in the
inventory, and commands like generate or clean may miss or remove their keys.
Workarounds:
- Add a
lib.rstarget and move derived types into it. - Move shared localization types into a small library crate and depend on it from your binary.