Skip to main content

Crate es_fluent_manager_bevy

Crate es_fluent_manager_bevy 

Source
Expand description

Docs Crates.io

§es-fluent-manager-bevy

online demo

Seamless Bevy integration for es-fluent.

This plugin connects es-fluent’s type-safe localization with Bevy’s ECS and Asset system. It allows you to use standard #[derive(EsFluent)] types as components that automatically update when the app/game’s language changes.

es-fluent-manager-bevybevy
crates.io
0.18.x0.18.x
0.17.x0.17.x

§Features

  • Asset Loading: Loads .ftl files via Bevy’s AssetServer.
  • Hot Reloading: Supports hot-reloading of translations during development.
  • Reactive UI: The FluentText component automatically refreshes text when the locale changes.
  • Global Hook Ownership: Can either let Bevy own es-fluent’s process-global localization bridge or fail fast when another integration already installed one.

§Quick Start

§1. Define the Module

In your crate root (lib.rs or main.rs), tell the manager to scan your assets:

// a i18n.toml file must exist in the root of the crate
es_fluent_manager_bevy::define_i18n_module!();

§2. Initialize & Use

Add the plugin to your App and define your I18n module:

use bevy::prelude::*;
use es_fluent_manager_bevy::I18nPlugin;
use unic_langid::langid;

// a i18n.toml file must exist in the root of the crate
es_fluent_manager_bevy::define_i18n_module!();

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(I18nPlugin::with_language(langid!("en-US")))
        .run();
}

I18nPlugin still installs the bridge that makes #[derive(EsFluent)] work inside Bevy, but it now defaults to GlobalLocalizerMode::ErrorIfAlreadySet. That keeps startup fail-fast if another integration already owns the process-global localization bridge.

If your Bevy app intentionally owns that hook and should override any previous registration, opt in explicitly:

use es_fluent_manager_bevy::{GlobalLocalizerMode, I18nPlugin};

App::new().add_plugins(
    I18nPlugin::with_language(langid!("en-US"))
        .with_global_localizer_mode(GlobalLocalizerMode::ReplaceExisting),
);

Plugin startup always uses strict module discovery, so invalid or duplicate registrations fail the app boot instead of being normalized silently. Failed hot reloads or locale switches keep the last accepted locale active instead of publishing a broken update.

Use RequestedLanguageId to read the latest user intent and ActiveLanguageId to read the currently published locale. LocaleChangedEvent refers to ActiveLanguageId, not merely the latest request.

Prefer the BevyFluentText derive macro. It auto-registers your type with I18nPlugin via inventory, so you don’t have to call any registration functions manually.

If a field depends on the active locale (like the Languages enum from es_fluent_lang), mark it with #[locale]. The macro will generate RefreshForLocale and register the locale-aware systems for you. #[locale] is supported on named struct fields and named enum variant fields, and you can mark more than one named field in the same variant when they all need refresh behavior.

RefreshForLocale receives the originally requested locale, not the fallback resource locale. For example, if en-GB falls back to en assets, locale-aware fields still refresh with en-GB.

use bevy::prelude::Component;
use es_fluent::EsFluent;
use es_fluent_manager_bevy::BevyFluentText;

#[derive(BevyFluentText, Clone, Component, EsFluent)]
pub enum UiMessage {
    StartGame,
    Settings,
    LanguageHint {
        #[locale]
        current_language: Languages,
    },
}

§4. Using in UI

Use the FluentText component wrapper for any type that implements ToFluentString (which #[derive(EsFluent)] provides).

use es_fluent_manager_bevy::FluentText;

fn spawn_menu(mut commands: Commands) {
    commands.spawn((
        // This text will automatically update if language changes
        FluentText::new(UiMessage::StartGame),
        Text::new(""),
    ));
}

§Manual Registration (Fallback)

If you cannot derive BevyFluentText (e.g., external types), you can still register manually:

app.register_fluent_text::<UiMessage>();

If the type needs locale refresh, implement RefreshForLocale and use the locale-aware registration function:

use es_fluent_manager_bevy::RefreshForLocale;

#[derive(EsFluent, Clone, Component)]
pub enum UiMessage {
    LanguageHint { current_language: Languages },
}

impl RefreshForLocale for UiMessage {
    fn refresh_for_locale(&mut self, lang: &unic_langid::LanguageIdentifier) {
        match self {
            UiMessage::LanguageHint { current_language } => {
                if let Ok(value) = Languages::try_from(lang) {
                    *current_language = value;
                }
            }
        }
    }
}

app.register_fluent_text_from_locale::<UiMessage>();

§Do Nested Types Need BevyFluentText?

Only the component type wrapped by FluentText<T> needs registration. If a nested field (like KbKeys) is only used inside a registered component, it does not need BevyFluentText. When the parent component re-renders, its EsFluent implementation formats all fields using the current locale.

You only need BevyFluentText for a nested type if you plan to use it directly as FluentText<ThatType> or otherwise register it as its own component.

Re-exports§

pub use bevy;
pub use inventory;
pub use unic_langid;
pub use components::*;
pub use plugin::*;
pub use systems::*;

Modules§

components
plugin
systems

Macros§

define_i18n_module
Defines a Bevy i18n module.

Structs§

ActiveLanguageId
A Bevy resource that holds the currently published active LanguageIdentifier.
EsFluentBevyPlugin
A plugin that initializes the es-fluent Bevy integration.
FtlAsset
A Bevy asset representing a Fluent Translation List (.ftl) file.
FtlAssetLoader
An AssetLoader for loading .ftl files as FtlAssets.
I18nAssets
A Bevy resource that manages the loading of FtlAssets.
I18nBundle
A Bevy resource containing per-locale Fluent bundles plus accepted resources used for locale fallback lookups.
I18nResource
The main resource for handling localization.
LocaleChangeEvent
A Bevy Message sent to request a change of the requested locale.
LocaleChangedEvent
A Bevy Message sent after the active locale has been successfully published.
RequestedLanguageId
A Bevy resource that holds the most recently requested LanguageIdentifier.

Traits§

BevyFluentTextRegistration
Trait for auto-registering FluentText systems with Bevy.
FluentDisplay
This trait is similar to std::fmt::Display, but it is used for formatting types that can be displayed in a Fluent message.
FluentTextRegistration
An extension trait for App to simplify the registration of FluentText components.
FromLocale
A trait for types that can be constructed from a LanguageIdentifier.
RefreshForLocale
A trait for types that can be updated in place when the locale changes.
ToFluentString
This trait is automatically implemented for any type that implements FluentDisplay.

Functions§

primary_language
Returns the primary language subtag from a LanguageIdentifier.
update_values_on_locale_change
A Bevy system that listens for LocaleChangedEvents and updates components that implement RefreshForLocale.

Derive Macros§

BevyFluentText
Registers a type for use with FluentText<T> in Bevy.