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.
- Integration via a embedded singleton manager (
es-fluent-manager-embedded) or for Bevy (es-fluent-manager-bevy).
Examples
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`
= "*"
To bootstrap .ftl files from your Rust types, add the build helper:
[]
= "*"
And create a build.rs:
// build.rs
use FluentParseMode;
Project configuration
Create an i18n.toml next to your Cargo.toml:
# i18n.toml
= "i18n" # where your localized files live
= "en"
When 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 or a struct to generate message IDs and implement es_fluent::FluentDisplay.
use EsFluent;
;
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 ;
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
}.
#[derive(EsFluent)] on structs (keys and “this”)
You can derive on structs to produce key enums (labels, descriptions, etc.). For example:
use EsFluent;
// generates `Address::this_ftl()`
This expands to enums like AddressLabelFtl and AddressDescriptionFtl with variants for each field (Street, PostalCode). this adds a helper Address::this_ftl() that returns the ID of the parent.
#[derive(EsFluentKv)] on structs
For key-value generation from structs, you can use EsFluentKv. This derive is specialized for generating keys for struct fields, often used for UI elements like labels and descriptions.
Here is an example of a User struct with various fields:
use ;
use Decimal;
use EnumIter;
The #[fluent_kv(this, keys = ["description", "label"])] attribute instructs the derive to generate enums UserDescriptionFtl and UserLabelFtl. The this argument also generates a message ID for the struct itself.
This will generate the following FTL entries:
## EnumCountry
enum_country-China = China
enum_country-France = France
enum_country-UnitedStates = United States
## PreferedLanguage
prefered_language-Chinese = Chinese
prefered_language-English = English
prefered_language-French = French
## User
user = User
## UserDescriptionFtl
user_description_kv_ftl = User Description Ftl
user_description_kv_ftl-age = Age
user_description_kv_ftl-balance = Balance
user_description_kv_ftl-birth_date = Birth Date
user_description_kv_ftl-country = Country
user_description_kv_ftl-email = Email
user_description_kv_ftl-enable_notifications = Enable Notifications
user_description_kv_ftl-preferred = Preferred
user_description_kv_ftl-skip_me = Skip Me
user_description_kv_ftl-subscribe_newsletter = Subscribe Newsletter
user_description_kv_ftl-username = Username
## UserLabelFtl
user_label_kv_ftl = User Label Ftl
user_label_kv_ftl-age = Age
user_label_kv_ftl-balance = Balance
user_label_kv_ftl-birth_date = Birth Date
user_label_kv_ftl-country = Country
user_label_kv_ftl-email = Email
user_label_kv_ftl-enable_notifications = Enable Notifications
user_label_kv_ftl-preferred = Preferred
user_label_kv_ftl-skip_me = Skip Me
user_label_kv_ftl-subscribe_newsletter = Subscribe Newsletter
user_label_kv_ftl-username = Username
Derive Macro Supported kinds
Enums
- enum_unit
- enum_named
- enum_tuple
Structs
- struct_named
- struct_tuple
Generics
Generic parameters must convert into Fluent values when used as arguments:
use EsFluent;
use FluentValue;
Language Enum Generation
#[es_fluent_language]
This macro reads your crate's i18n.toml, finds all available languages in your assets_dir, and generates an enum with a variant for each one. It also implements Default (using your fallback_language) and conversions to/from unic_langid::LanguageIdentifier.
Usage
Add the dependencies:
[]
= "*"
= "*"
Then, apply the macro to an empty enum:
use EsFluent;
use es_fluent_language;
use EnumIter;
// The macro generates variants from your i18n asset folders.
// If you have 'en' and 'fr-CA', it generates:
// enum Language { En, FrCA }