Crate es_fluent

Crate es_fluent 

Source
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_dir

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 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§

fluent_choice
fluent_display

Traits§

EsFluentChoice
This trait is used to convert an enum into a string that can be used as a Fluent choice.
FluentDisplay
This trait is similar to std::fmt::Display, but it is used for formatting types that can be displayed in a Fluent message.
ToFluentString
This trait is automatically implemented for any type that implements FluentDisplay.

Derive Macros§

EsFluent
EsFluentChoice