Expand description
§es-fluent-manager-bevy
Seamless Bevy integration for es-fluent.
This plugin connects es-fluent’s type-safe localization with Bevy’s ECS and
Asset system. Use #[derive(EsFluent)] for typed messages, wrap them in
FluentText<T> for UI text, and derive BevyFluentText on message types used
with FluentText<T> to register automatic updates when the app/game’s language
changes.
es-fluent-manager-bevy | bevy |
|---|---|
| crates.io | |
0.19.x | 0.19.x |
§Features
- Owner Resources: Registers generated module resources from the crate that owns the localization domain as Bevy embedded assets.
- Asset Loading: Custom metadata-only registrations can still load
.ftlfiles via Bevy’sAssetServer. - Hot Reloading: Supports hot-reloading for resources loaded through Bevy’s asset pipeline during development.
- Reactive UI: The
FluentTextcomponent automatically refreshes text when the locale changes. - Bevy-native Context: Systems can request
BevyI18nas aSystemParamfor direct localization. - Explicit Context: Localization uses Bevy resources and
BevyI18nsystem params. - Composable Scheduling: Runtime and text-refresh systems are labeled with
I18nSetfor normal Bevy ordering.
§Quick Start
§1. Define the Module
Prefer a library-reachable module, usually src/i18n.rs declared from
src/lib.rs, so cargo es-fluent generate can discover localizable types from
the library target:
// a i18n.toml file must exist in the root of the crate
es_fluent_manager_bevy::define_i18n_module!();Putting the module macro only in src/main.rs is runtime-only. It is safe only
when derived message types are still reachable from a library target, or when
you accept that binary-only derived types are not discovered by the CLI.
§2. Initialize & Use
Add the plugin to your App:
use bevy::prelude::*;
use es_fluent_manager_bevy::I18nPlugin;
use unic_langid::langid;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(I18nPlugin::with_language(langid!("en")))
.run();
}Generated Bevy module registrations register the owning crate’s .ftl files
from that crate’s configured assets_dir as Bevy embedded assets; consuming
apps should not copy dependency-owned domain files into their own asset tree.
asset_path is only used for custom metadata-only registrations that do not
provide owner embedded assets.
If your Bevy asset root is assets but those custom resources live in
assets/i18n, configure the path explicitly:
use es_fluent_manager_bevy::{I18nPlugin, I18nPluginConfig};
app.add_plugins(I18nPlugin::with_config(
I18nPluginConfig::new(langid!("en")).with_asset_path("i18n"),
));§Advanced behavior
Plugin startup uses strict module discovery, so invalid or duplicate
registrations are reported through I18nPluginStartupError. When setup fails,
the plugin skips localization runtime setup and leaves the error resource in the
app world for diagnostics. Failed hot reloads or locale switches keep the last
accepted locale active. A failed hot reload records diagnostics but keeps the
previous ready cache selectable until a later rebuild succeeds.
Generated message lookup is domain-scoped. If separate domains define the same message ID, Bevy keeps typed domain-scoped lookup available and leaves raw unscoped lookup unavailable for the ambiguous merged locale.
Locales with only optional resources, or with missing optional resources, are treated as ready and publish an empty Bevy cache.
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. When a requested locale
falls back to a resolved locale, Bevy publishes the requested locale for change
events and ECS resources while using the resolved locale for ready bundle
lookup. Runtime fallback managers are best-effort: Bevy asks them to select the
requested locale first, then the resolved locale, but rejection does not block
Bevy resource-backed locale publication. Metadata-only Bevy registrations
create Bevy resource availability either from owner-provided embedded asset
handles or, for custom registrations, Bevy asset handles. Runtime localizer
registrations are reserved for the fallback manager and do not make a locale
wait on Bevy resource bundles.
When attached, runtime fallback selection tells FluentManager that Bevy assets
have already proved application locale support, so follower-only utility modules
such as es-fluent-lang can be committed without making runtime-only locales
selectable. Generated embedded localizers are fallback-aware, while custom
runtime localizers should implement parent-locale fallback in
select_language(...) when they need it. Runtime fallback managers are attached
whenever runtime modules are discovered, even if they reject the startup locale.
A startup rejection leaves runtime localizers unselected until a later accepted
locale switch. Runtime fallback managers are used only after Bevy resolves a
locale through asset or ready-bundle availability during startup or a later
LocaleChangeEvent; runtime-only locales do not by themselves make a Bevy
locale switch selectable.
For direct localization inside a system, request BevyI18n like any other
Bevy system parameter:
use es_fluent::FluentLabel as _;
use es_fluent_manager_bevy::BevyI18n;
fn update_title(i18n: BevyI18n) {
let title = i18n.localize_message(&UiMessage::Settings);
// `SettingsPanel` is any type that derives `EsFluentLabel`.
let section_title = SettingsPanel::localize_label(&i18n);
// apply `title` to your Bevy UI, window, or gameplay state
// use `section_title` for an `EsFluentLabel` type label
}§Schedule Ordering
I18nPlugin labels its systems with I18nSet so app systems can use Bevy’s
standard .before(...) and .after(...) ordering APIs. AssetWatch,
AssetLoading, BundleRebuild, LocaleChange, and LocaleSync run in
Update. TextUpdate runs in PostUpdate after locale-aware FluentText
values have refreshed and Bevy Text components have been written.
use bevy::prelude::*;
use es_fluent_manager_bevy::I18nSet;
fn persist_locale_choice() {}
fn sync_window_title() {}
app.add_systems(Update, persist_locale_choice.after(I18nSet::LocaleSync));
app.add_systems(PostUpdate, sync_window_title.after(I18nSet::TextUpdate));§3. Define Localizable Components (Recommended)
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 multiple named fields in the same variant refresh together. Each
#[locale] field type must implement TryFrom<&LanguageIdentifier>.
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
FluentMessage (which #[derive(EsFluent)] provides).
use es_fluent_manager_bevy::FluentText;
fn spawn_menu(mut commands: Commands) {
commands.spawn((
FluentText::new(UiMessage::StartGame),
Text::new(""),
));
}§Manual Registration
If you cannot derive BevyFluentText (for example, for external types), register manually:
app.register_fluent_text::<UiMessage>();Macros§
- define_
i18n_ module - Defines a Bevy i18n module.
Structs§
- Active
Language Id - A Bevy resource that holds the currently published active
LanguageIdentifier. - Bevy
I18n - Bevy-native localization context for systems.
- Fluent
Text - A Bevy component that holds localized text content.
- I18n
Plugin - Bevy plugin that wires asset loading, runtime language state, and the context-bound localization together.
- I18n
Plugin Config - Configuration for
I18nPlugin. - I18n
Plugin Startup Error - Startup failure captured when the plugin cannot safely initialize i18n.
- Locale
Change Event - A Bevy
Messagesent to request a change of the requested locale. - Locale
Changed Event - A Bevy
Messagesent after the active locale has been successfully published. - Requested
Language Id - A Bevy resource that holds the most recently requested
LanguageIdentifier.
Enums§
- I18nSet
- Bevy system sets used by
crate::I18nPlugin.
Traits§
- Fluent
Text Registration - An extension trait for
Appto simplify the registration ofFluentTextcomponents. - From
Locale - A trait for types that can be constructed from a
LanguageIdentifier. - Refresh
ForLocale - A trait for types that can be updated in place when the locale changes.
Derive Macros§
- Bevy
Fluent Text - Registers a type for use with
FluentText<T>in Bevy.