Expand description
§es-fluent
Derive macros and utilities for authoring strongly-typed messages with Project Fluent.
This crate gives you:
- Derives to turn enums/structs into Fluent message IDs and arguments.
- A simple API to format values for Fluent and convert them into strings.
- Optional integration via a embedded singleton manager (
es-fluent-manager-embedded) or for Bevy (es-fluent-manager-bevy).
§Installation
Add the crate with the derive feature to access the procedural macros:
[dependencies]
es-fluent = { version = "*", features = ["derive"] }
unic-langid = "*"
# If you want to register modules with the embedded singleton and localize at runtime:
es-fluent-manager-embedded = "*"
# For Bevy integration: replace `es-fluent-manager-embedded` with `es-fluent-manager-bevy`
es-fluent-manager-bevy = "*"To bootstrap .ftl files from your Rust types, add the build helper:
[build-dependencies]
es-fluent-build = "*"And create a build.rs:
// build.rs
use es_fluent_build::FluentParseMode;
fn main() {
if let Err(e) = es_fluent_build::FluentBuilder::new()
.mode(FluentParseMode::Conservative)
.build()
{
eprintln!("Error building FTL files: {e}");
}
}§Project configuration
Create an i18n.toml next to your Cargo.toml:
# i18n.toml
assets_dir = "i18n" # where your localized files live
fallback_language = "en" # default language subdirectory under assets_dirWhen you run a build, the builder will:
- Discover your crate name,
- Parse Rust sources under
src/, - Generate or update a base FTL file at
{assets_dir}/{fallback_language}/{crate_name}.ftl.
For example, with assets_dir = "../i18n" and fallback_language = "en", the file would be ../i18n/en/{crate_name}.ftl.
§Core derives
§#[derive(EsFluent)] on enums
Annotate an enum to generate message IDs and, optionally, implement es_fluent::FluentDisplay or std::fmt::Display.
use es_fluent::EsFluent;
#[derive(EsFluent)]
#[fluent(display = "fluent")] // default; use "std" to implement std::fmt::Display
pub enum Hello<'a> {
User { user_name: &'a str },
}Fields become Fluent arguments. The derive generates stable keys and formatting logic for you.
§Choices with EsFluentChoice
When a message needs to match on an enum (a Fluent select expression), implement EsFluentChoice. You can then mark a field with #[fluent(choice)] to pass its choice value instead of formatting it as a nested message.
use es_fluent::{EsFluent, EsFluentChoice};
#[derive(EsFluent, EsFluentChoice)]
#[fluent_choice(serialize_all = "snake_case")]
pub enum Gender {
Male,
Female,
Other,
}
#[derive(EsFluent)]
pub enum Shared<'a> {
Photos {
user_name: &'a str,
photo_count: &'a u32,
#[fluent(choice)]
user_gender: &'a Gender,
},
}A prototyping build will write skeleton FTL like:
## Gender
gender-Male = Male
gender-Female = Female
gender-Other = Other
## Hello
hello-User = User { $user_name }
## Shared
shared-Photos = Photos { $user_name } { $photo_count } { $user_gender }You can then edit it into a real copy, e.g.:
## Gender
gender-Female = Female
gender-Helicopter = Helicopter
gender-Male = Male
gender-Other = Other
## Hello
hello-User = Hello, {$user_name}!
## Shared
shared-Photos =
{$user_name} {$photo_count ->
[one] added a new photo
*[other] added {$photo_count} new photos
} to {$user_gender ->
[male] his stream
[female] her stream
*[other] their stream
}.§Display strategy
By default, EsFluent implements es_fluent::FluentDisplay, which formats through Fluent. If you prefer plain Rust Display for a type, use:
#[derive(EsFluent)]
#[fluent(display = "std")]
pub enum AbcStdDisplay {
A, B, C,
}This also works with strum::EnumDiscriminants when you want to display the discriminants.
§#[derive(EsFluent)] on structs (keys and “this”)
You can derive on structs to produce key enums (labels, descriptions, etc.). For example:
use es_fluent::EsFluent;
#[derive(EsFluent)]
#[fluent(display = "std")]
#[fluent(this)] // generates `Address::this_ftl()`
#[fluent(keys = ["Description", "Label"])]
pub struct Address {
pub street: String,
pub postal_code: String,
}This expands to enums like AddressLabelFtl and AddressDescriptionFtl with variants for each field (Street, PostalCode). They implement the selected display strategy. this adds a helper Address::this_ftl() that returns the ID of the parent.
§Derive Macro Supported kinds
§Enums
- enum_unit
- enum_named
- enum_tuple
§Structs
- struct_named
§Generics
Generic parameters must convert into Fluent values when used as arguments:
use es_fluent::EsFluent;
use fluent_bundle::FluentValue;
#[derive(EsFluent)]
pub enum GenericFluentDisplay<T>
where
for<'a> &'a T: Into<FluentValue<'a>>,
{
A(T),
B { c: T },
D,
}§Examples
Modules§
Traits§
- EsFluent
Choice - This trait is used to convert an enum into a string that can be used as a Fluent choice.
- Fluent
Display - This trait is similar to
std::fmt::Display, but it is used for formatting types that can be displayed in a Fluent message. - ToFluent
String - This trait is automatically implemented for any type that implements
FluentDisplay.