34 releases
Uses new Rust 2024
| new 0.18.11 | Apr 6, 2026 |
|---|---|
| 0.18.10 | Mar 27, 2026 |
| 0.18.9 | Feb 21, 2026 |
| 0.18.2 | Jan 31, 2026 |
| 0.17.5 | Oct 20, 2025 |
#417 in Internationalization (i18n)
160KB
3K
SLoC
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. It allows you to use standard #[derive(EsFluent)] types as components that automatically update when the app/game's language changes.
es-fluent-manager-bevy |
bevy |
|---|---|
| crates.io | |
0.18.x |
0.18.x |
0.17.x |
0.17.x |
Features
- Asset Loading: Loads
.ftlfiles via Bevy'sAssetServer. - Hot Reloading: Supports hot-reloading of translations during development.
- Reactive UI: The
FluentTextcomponent automatically refreshes text when the locale changes. - Global Hook: Integrates with
es-fluent's global state.
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_embedded::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)
// Initialize with default language
.add_plugins(I18nPlugin::with_language(langid!("en-US")))
.run();
}
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.
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.
Dependencies
~30–51MB
~776K SLoC