es-fluent
Derive macros and utilities for authoring strongly-typed messages with Project Fluent.
This framework gives you:
- Derives to turn enums/structs into Fluent message IDs and arguments.
- A cli to generate ftl files skeleton and other utilities.
- Language Enum Generation
- Integration via a embedded singleton manager or es-fluent-manager-bevy for bevy
Examples
Used in
Installation
Add the crate with the derive feature to access the procedural macros:
[]
= { = "*", = ["derive"] }
= "*"
# If you want to register modules with the embedded singleton and localize at runtime:
= "*"
# For Bevy integration: replace `es-fluent-manager-embedded` with `es-fluent-manager-bevy`
= "*"
Project configuration
Create an i18n.toml next to your Cargo.toml:
# Default fallback language (required)
= "en-US"
# Path to FTL assets relative to the config file (required)
= "assets/locales"
# Features to enable if the crate’s es-fluent derives are gated behind a feature (optional)
= ["my-feature"]
# Optional allowlist of namespace values for FTL file splitting
= ["ui", "errors", "messages"]
Incremental builds for locale assets
The manager macros discover locales at compile time. To ensure locale folder/file
renames (for example fr to fr-FR) trigger rebuilds,
enable the build feature of es-fluent in build dependencies and call the
tracking helper from build.rs.
[]
= { = "*", = ["build"] }
// build.rs
Namespaces (optional)
You can route specific types into separate .ftl files by adding a namespace. All derive macros support the same namespace options:
EsFluent
use EsFluent;
;
EsFluentThis
use EsFluentThis;
;
;
EsFluentVariants
use EsFluentVariants;
Output Layout
- Default:
assets_dir/{locale}/{crate}.ftl - Namespaced:
assets_dir/{locale}/{crate}/{namespace}.ftl
When namespaces are used, namespace files are treated as the canonical split
for that locale. {crate}.ftl remains optional for backwards compatibility.
Namespace Values
namespace = "name"- explicit namespace stringnamespace = file- uses the source file stem (e.g.,src/ui/button.rs->button)namespace(file(relative))- uses the file path relative to the crate root, stripssrc/, and removes the extension (e.g.,src/ui/button.rs->ui/button)namespace = folder- uses the source file parent folder (e.g.,src/ui/button.rs->ui)namespace(folder(relative))- uses the parent folder path relative to the crate root, stripssrc/when nested, and keepssrcfor root module files (e.g.,src/ui/button.rs->ui)
If namespaces = [...] is set in i18n.toml, both the compiler (at compile-time) and the CLI will validate that string-based namespaces used by your code are in that allowlist.
Derives
#[derive(EsFluent)]
Turns an enum or struct into a localizable message.
- Enums: Each variant becomes a message ID (e.g.,
MyEnum::Variant->my_enum-Variant). - Structs: The struct itself becomes the message ID (e.g.,
MyStruct->my_struct). - Fields: Fields are automatically exposed as arguments to the Fluent message.
use ;
use ToFluentString;
let _ = InvalidPassword.to_fluent_string;
let _ = UserNotFound .to_fluent_string;
let _ = Something.to_fluent_string;
use ToFluentString;
let profile = UserProfile ;
let _ = profile.to_fluent_string;
#[derive(EsFluentChoice)]
Allows an enum to be used inside another message as a selector (e.g., for gender or status).
use ;
use ToFluentString;
let profile = UserProfile ;
let _ = profile.to_fluent_string;
#[derive(EsFluentVariants)]
Generates key-value pair enums for struct fields. This is perfect for generating UI labels, placeholders, or descriptions for a form object.
use EsFluentVariants;
// Generates enums -> keys:
// LoginFormLabelVariants::{Variants} -> (login_form_label_variants-{variant})
// LoginFormDescriptionVariants::{Variants} -> (login_form_description_variants-{variant})
use ToFluentString;
let _ = Username.to_fluent_string;
#[derive(EsFluentThis)]
Generates a helper implementation of the ThisFtl trait and registers the type's name as a key. This is similar to EsFluentVariants (which registers fields), but for the parent type itself.
#[fluent_this(origin)]: Generates an implementation wherethis_ftl()returns the base key for the type.
use EsFluentThis;
// Generates key:
// (gender_this)
use ThisFtl;
let _ = this_ftl;
#[fluent_this(variants)]: Can be combined withEsFluentVariantsderives to generate keys for variants.
// Generates keys:
// (login_form_label_variants_this)
// (login_form_description_variants_this)
use ThisFtl;
let _ = this_ftl;