When to use this skill
- Adding Fluent-based i18n to a Bevy app (
es-fluent-manager-bevy = "0.19.2"). - Defining typed UI messages with
#[derive(EsFluent)]and#[derive(BevyFluentText)]. - Wrapping a UI text entity with
FluentText<T>for automatic locale-driven refresh. - Switching locales at runtime via
LocaleChangeEvent. - Reading the current locale from
RequestedLanguageId.
Canonical pattern — 5-file minimum shape
Cargo.toml:
[dependencies]
bevy = "0.19"
es-fluent = { version = "0.18.1", features = ["derive"] }
es-fluent-manager-bevy = { version = "0.19.2", features = ["macros"] }
unic-langid = "0.9"
i18n.toml (crate root, read at compile time):
fallback_language = "en"
assets_dir = "assets/locales"
src/i18n.rs:
es_fluent_manager_bevy::define_i18n_module!();
src/lib.rs — message types must live here (see Gotchas):
use bevy::prelude::*;
use es_fluent::EsFluent;
use es_fluent_manager_bevy::{
BevyFluentText, FluentText, I18nPlugin, LocaleChangeEvent, RequestedLanguageId,
};
use unic_langid::langid;
pub mod i18n;
#[derive(BevyFluentText, Clone, EsFluent)]
#[fluent(namespace = "ui")]
pub enum UiMessage { StartGame, Settings, QuitGame }
pub fn build_i18n_plugin() -> I18nPlugin {
I18nPlugin::with_language(langid!("en"))
}
pub fn setup_ui(mut commands: Commands) {
commands.spawn(Camera2d);
// FluentText<T> writes translations into a sibling Text component.
commands.spawn((FluentText::new(UiMessage::StartGame), Text::new("")));
}
pub fn switch_locale_on_keypress(
keys: Res<ButtonInput<KeyCode>>,
requested: Res<RequestedLanguageId>,
mut locale_events: MessageWriter<LocaleChangeEvent>,
) {
if keys.just_pressed(KeyCode::KeyL) {
let next = if requested.0.to_string() == "en" { langid!("fr") } else { langid!("en") };
locale_events.write(LocaleChangeEvent(next));
}
}
src/main.rs — thin binary entry point:
use bevy::prelude::*;
use my_game::{build_i18n_plugin, setup_ui, switch_locale_on_keypress};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(build_i18n_plugin())
.add_systems(Startup, setup_ui)
.add_systems(Update, switch_locale_on_keypress)
.run();
}
Replace my_game with your package name (hyphens become underscores). The full
walkthrough — including the lib-vs-binary rationale, package-name placeholder,
and pure-binary-crate workarounds — is in
references/lib-target-layout.md.
Topics
| Topic | Reference |
|---|---|
Why types must live in the lib target; cargo es-fluent generate invisibility footgun; full layout |
references/lib-target-layout.md |
BevyFluentText derive vs FluentText<T> component; Text::new("") |
references/components.md |
LocaleChangeEvent (request) vs LocaleChangedEvent (confirmation); RequestedLanguageId; Messages vs Events |
references/locale-events.md |
i18n.toml full schema, assets_dir, I18nPluginConfig runtime override |
references/i18n-toml.md |
generate, watch, check, clean, sync, tree, format — dev and CI workflows |
references/cli.md |
rustc 1.96+ requirement; rust-toolchain.toml pin; failure modes on older toolchains |
references/toolchain.md |
Gotchas
-
cargo es-fluent generateonly sees the lib target. Message enums declared only insrc/main.rsare invisible to the CLI and produce empty.ftloutput with no error. Move everyBevyFluentText/EsFluent-derived type tosrc/lib.rs(or a module reachable from it). See references/lib-target-layout.md. -
BevyFluentTextis a derive macro;FluentText<T>is the component. The derive registers refresh systems viainventory. The component is what you spawn on UI entities. In 0.19.2 the message type needsFluentMessage + Clone + Send + Sync + 'static; it does not deriveComponentitself. See references/components.md. -
Minimum rustc 1.96. Older toolchains fail with cryptic trait-resolution errors that do not mention the version requirement. Pin with
rust-toolchain.toml([toolchain]/channel = "1.96"). See references/toolchain.md. -
Events are Messages. Use
MessageWriter<LocaleChangeEvent>andMessageReader<LocaleChangedEvent>— notEventWriter/EventReader(renamed in Bevy 0.17). See references/locale-events.md.
See also
bevy-ui—FluentText<T>is used alongsideText,Node, andButton.bevy-ecs-components—#[derive(Component)]patterns required byFluentText<T>.bevy-assets— how Bevy'sAssetServerloads.ftlfiles; relevant for hot-reload.